1
0
mirror of git://jb55.com/damus synced 2024-09-18 19:23:49 +00:00

Merge branch 'master' into exp-backoff

This commit is contained in:
Bryan Montz 2023-02-27 06:23:38 -06:00
commit 7c2e8a6cc5
127 changed files with 3337 additions and 849 deletions

View File

@ -1,3 +1,28 @@
## [1.1.0-9] - 2023-02-26
### Added
- Customized zaps (William Casarin)
- Add new Notifications View (William Casarin)
- Bookmarking (Joel Klabo)
### Changed
- No more inline npubs when tagging users (Swift)
### Fixed
- Fix alignment of side menu labels (Joel Klabo)
- Fix duplicated participants in reply-to view (Joel Klabo)
- Load missing profiles in Zaps view (William Casarin)
- Fix memory leak with inline videos (William Casarin)
- Eliminate popping when scrolling (William Casarin)
[1.1.0-9]: https://github.com/damus-io/damus/releases/tag/v1.1.0-9
## [1.1.0-3] - 2023-02-20
### Added

View File

@ -11,6 +11,11 @@
3169CAE6294E69C000EE4006 /* EmptyTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAE5294E69C000EE4006 /* EmptyTimelineView.swift */; };
3169CAED294FCCFC00EE4006 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAEC294FCCFC00EE4006 /* Constants.swift */; };
31D2E847295218AF006D67F8 /* Shimmer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D2E846295218AF006D67F8 /* Shimmer.swift */; };
3A3040ED29A5CB86008A0F29 /* ReplyDescriptionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3040EC29A5CB86008A0F29 /* ReplyDescriptionTests.swift */; };
3A3040EF29A8FEE9008A0F29 /* EventDetailBarTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3040EE29A8FEE9008A0F29 /* EventDetailBarTests.swift */; };
3A3040F129A8FF97008A0F29 /* LocalizationUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3040F029A8FF97008A0F29 /* LocalizationUtil.swift */; };
3A3040F329A91366008A0F29 /* ProfileViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3040F229A91366008A0F29 /* ProfileViewTests.swift */; };
3A30410129AB12AA008A0F29 /* EventGroupViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A30410029AB12AA008A0F29 /* EventGroupViewTests.swift */; };
3A4325A82961E11400BFCD9D /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 3A4325AA2961E11400BFCD9D /* Localizable.stringsdict */; };
3AA247FD297E3CFF0090C62D /* RepostsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA247FC297E3CFF0090C62D /* RepostsModel.swift */; };
3AA247FF297E3D900090C62D /* RepostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA247FE297E3D900090C62D /* RepostsView.swift */; };
@ -43,6 +48,11 @@
4C285C8C28398BC7008A31F1 /* Keys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C285C8B28398BC6008A31F1 /* Keys.swift */; };
4C285C8E28399BFE008A31F1 /* SaveKeysView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C285C8D28399BFD008A31F1 /* SaveKeysView.swift */; };
4C2CDDF7299D4A5E00879FD5 /* Debouncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2CDDF6299D4A5E00879FD5 /* Debouncer.swift */; };
4C30AC7229A5677A00E2BD5A /* NotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C30AC7129A5677A00E2BD5A /* NotificationsView.swift */; };
4C30AC7429A5680900E2BD5A /* EventGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C30AC7329A5680900E2BD5A /* EventGroupView.swift */; };
4C30AC7629A5770900E2BD5A /* NotificationItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C30AC7529A5770900E2BD5A /* NotificationItemView.swift */; };
4C30AC7829A577AB00E2BD5A /* EventCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C30AC7729A577AB00E2BD5A /* EventCache.swift */; };
4C30AC8029A6A53F00E2BD5A /* ProfilePicturesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C30AC7F29A6A53F00E2BD5A /* ProfilePicturesView.swift */; };
4C363A8428233689006E126D /* Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A8328233689006E126D /* Parser.swift */; };
4C363A8828236948006E126D /* BlocksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A8728236948006E126D /* BlocksView.swift */; };
4C363A8A28236B57006E126D /* MentionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A8928236B57006E126D /* MentionView.swift */; };
@ -93,6 +103,9 @@
4C3EA67F28FFC01D00C48A62 /* InvoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3EA67E28FFC01D00C48A62 /* InvoiceView.swift */; };
4C42812C298C848200DBF26F /* TranslateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C42812B298C848200DBF26F /* TranslateView.swift */; };
4C477C9E282C3A4800033AA3 /* TipCounter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C477C9D282C3A4800033AA3 /* TipCounter.swift */; };
4C54AA0729A540BA003E4487 /* NotificationsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C54AA0629A540BA003E4487 /* NotificationsModel.swift */; };
4C54AA0A29A55429003E4487 /* EventGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C54AA0929A55429003E4487 /* EventGroup.swift */; };
4C54AA0C29A5543C003E4487 /* ZapGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C54AA0B29A5543C003E4487 /* ZapGroup.swift */; };
4C5C7E68284ED36500A22DF5 /* SearchHomeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5C7E67284ED36500A22DF5 /* SearchHomeModel.swift */; };
4C5C7E6A284EDE2E00A22DF5 /* SearchResultsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5C7E69284EDE2E00A22DF5 /* SearchResultsView.swift */; };
4C5F9114283D694D0052CD1C /* FollowTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5F9113283D694D0052CD1C /* FollowTarget.swift */; };
@ -122,6 +135,8 @@
4C90BD1C283AC38E008EE7EF /* Bech32Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD1B283AC38E008EE7EF /* Bech32Tests.swift */; };
4C987B57283FD07F0042CE38 /* FollowersModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C987B56283FD07F0042CE38 /* FollowersModel.swift */; };
4C99737B28C92A9200E53835 /* ChatroomMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C99737A28C92A9200E53835 /* ChatroomMetadata.swift */; };
4C9F18E229AA9B6C008C55EC /* CustomizeZapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9F18E129AA9B6C008C55EC /* CustomizeZapView.swift */; };
4C9F18E429ABDE6D008C55EC /* MaybeAnonPfpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9F18E329ABDE6D008C55EC /* MaybeAnonPfpView.swift */; };
4CA2EFA0280E37AC0044ACD8 /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */; };
4CAAD8AD298851D000060CEA /* AccountDeletion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CAAD8AC298851D000060CEA /* AccountDeletion.swift */; };
4CAAD8B029888AD200060CEA /* RelayConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CAAD8AF29888AD200060CEA /* RelayConfigView.swift */; };
@ -216,6 +231,7 @@
7C95CAEE299DCEF1009DCB67 /* KFOptionSetter+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C95CAED299DCEF1009DCB67 /* KFOptionSetter+.swift */; };
7CFF6317299FEFE5005D382A /* SelectableText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CFF6316299FEFE5005D382A /* SelectableText.swift */; };
9609F058296E220800069BF3 /* BannerImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9609F057296E220800069BF3 /* BannerImageView.swift */; };
9C83F89329A937B900136C08 /* TextViewWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9C83F89229A937B900136C08 /* TextViewWrapper.swift */; };
BA693074295D649800ADDB87 /* UserSettingsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA693073295D649800ADDB87 /* UserSettingsStore.swift */; };
BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */; };
DD597CBD2963D85A00C64D32 /* MarkdownTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD597CBC2963D85A00C64D32 /* MarkdownTests.swift */; };
@ -258,6 +274,18 @@
3A25EF142992DA5D008ABE69 /* el-GR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "el-GR"; path = "el-GR.lproj/Localizable.strings"; sourceTree = "<group>"; };
3A25EF152992DA5D008ABE69 /* el-GR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "el-GR"; path = "el-GR.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
3A2B8B0A296A8982009CC16D /* en-US */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "en-US"; path = "en-US.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
3A3040EC29A5CB86008A0F29 /* ReplyDescriptionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyDescriptionTests.swift; sourceTree = "<group>"; };
3A3040EE29A8FEE9008A0F29 /* EventDetailBarTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventDetailBarTests.swift; sourceTree = "<group>"; };
3A3040F029A8FF97008A0F29 /* LocalizationUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizationUtil.swift; sourceTree = "<group>"; };
3A3040F229A91366008A0F29 /* ProfileViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewTests.swift; sourceTree = "<group>"; };
3A3040F929A91ED6008A0F29 /* zh-HK */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-HK"; path = "zh-HK.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
3A3040FA29A91EFC008A0F29 /* zh-HK */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-HK"; path = "zh-HK.lproj/Localizable.strings"; sourceTree = "<group>"; };
3A3040FB29A91F03008A0F29 /* zh-HK */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "zh-HK"; path = "zh-HK.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
3A3040FC29A91F31008A0F29 /* zh-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-TW"; path = "zh-TW.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
3A3040FD29A91F31008A0F29 /* zh-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "zh-TW"; path = "zh-TW.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
3A3040FE29A91F31008A0F29 /* zh-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-TW"; path = "zh-TW.lproj/Localizable.strings"; sourceTree = "<group>"; };
3A3040FF29AB02D1008A0F29 /* en-US */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-US"; path = "en-US.lproj/Localizable.strings"; sourceTree = "<group>"; };
3A30410029AB12AA008A0F29 /* EventGroupViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventGroupViewTests.swift; sourceTree = "<group>"; };
3A41E559299D52BE001FA465 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/InfoPlist.strings; sourceTree = "<group>"; };
3A41E55A299D52BE001FA465 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/Localizable.strings; sourceTree = "<group>"; };
3A41E55B299D52BE001FA465 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = id; path = id.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
@ -331,6 +359,11 @@
4C285C8B28398BC6008A31F1 /* Keys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keys.swift; sourceTree = "<group>"; };
4C285C8D28399BFD008A31F1 /* SaveKeysView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveKeysView.swift; sourceTree = "<group>"; };
4C2CDDF6299D4A5E00879FD5 /* Debouncer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Debouncer.swift; sourceTree = "<group>"; };
4C30AC7129A5677A00E2BD5A /* NotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsView.swift; sourceTree = "<group>"; };
4C30AC7329A5680900E2BD5A /* EventGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventGroupView.swift; sourceTree = "<group>"; };
4C30AC7529A5770900E2BD5A /* NotificationItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationItemView.swift; sourceTree = "<group>"; };
4C30AC7729A577AB00E2BD5A /* EventCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventCache.swift; sourceTree = "<group>"; };
4C30AC7F29A6A53F00E2BD5A /* ProfilePicturesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePicturesView.swift; sourceTree = "<group>"; };
4C363A8328233689006E126D /* Parser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Parser.swift; sourceTree = "<group>"; };
4C363A8728236948006E126D /* BlocksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlocksView.swift; sourceTree = "<group>"; };
4C363A8928236B57006E126D /* MentionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionView.swift; sourceTree = "<group>"; };
@ -411,6 +444,9 @@
4C42812B298C848200DBF26F /* TranslateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslateView.swift; sourceTree = "<group>"; };
4C477C9D282C3A4800033AA3 /* TipCounter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipCounter.swift; sourceTree = "<group>"; };
4C4A3A5A288A1B2200453788 /* damus.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = damus.entitlements; sourceTree = "<group>"; };
4C54AA0629A540BA003E4487 /* NotificationsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsModel.swift; sourceTree = "<group>"; };
4C54AA0929A55429003E4487 /* EventGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventGroup.swift; sourceTree = "<group>"; };
4C54AA0B29A5543C003E4487 /* ZapGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZapGroup.swift; sourceTree = "<group>"; };
4C5C7E67284ED36500A22DF5 /* SearchHomeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchHomeModel.swift; sourceTree = "<group>"; };
4C5C7E69284EDE2E00A22DF5 /* SearchResultsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchResultsView.swift; sourceTree = "<group>"; };
4C5F9113283D694D0052CD1C /* FollowTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowTarget.swift; sourceTree = "<group>"; };
@ -440,6 +476,8 @@
4C90BD1B283AC38E008EE7EF /* Bech32Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bech32Tests.swift; sourceTree = "<group>"; };
4C987B56283FD07F0042CE38 /* FollowersModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowersModel.swift; sourceTree = "<group>"; };
4C99737A28C92A9200E53835 /* ChatroomMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatroomMetadata.swift; sourceTree = "<group>"; };
4C9F18E129AA9B6C008C55EC /* CustomizeZapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomizeZapView.swift; sourceTree = "<group>"; };
4C9F18E329ABDE6D008C55EC /* MaybeAnonPfpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaybeAnonPfpView.swift; sourceTree = "<group>"; };
4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineView.swift; sourceTree = "<group>"; };
4CAAD8AC298851D000060CEA /* AccountDeletion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDeletion.swift; sourceTree = "<group>"; };
4CAAD8AF29888AD200060CEA /* RelayConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayConfigView.swift; sourceTree = "<group>"; };
@ -536,6 +574,7 @@
7C95CAED299DCEF1009DCB67 /* KFOptionSetter+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KFOptionSetter+.swift"; sourceTree = "<group>"; };
7CFF6316299FEFE5005D382A /* SelectableText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectableText.swift; sourceTree = "<group>"; };
9609F057296E220800069BF3 /* BannerImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BannerImageView.swift; sourceTree = "<group>"; };
9C83F89229A937B900136C08 /* TextViewWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextViewWrapper.swift; sourceTree = "<group>"; };
BA693073295D649800ADDB87 /* UserSettingsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettingsStore.swift; sourceTree = "<group>"; };
BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectWalletView.swift; sourceTree = "<group>"; };
DD597CBC2963D85A00C64D32 /* MarkdownTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkdownTests.swift; sourceTree = "<group>"; };
@ -651,6 +690,7 @@
4C0A3F8D280F63FF000448DE /* Models */ = {
isa = PBXGroup;
children = (
4C54AA0829A55416003E4487 /* Notifications */,
3AA247FC297E3CFF0090C62D /* RepostsModel.swift */,
4C0A3F8E280F640A000448DE /* ThreadModel.swift */,
4C0A3F92280F66F5000448DE /* ReplyMap.swift */,
@ -690,13 +730,35 @@
3AAA95CB298E07E900F3D526 /* DeepLPlan.swift */,
4CE8795A2996C47A00F758CC /* ZapsModel.swift */,
3AA59D1C2999B0400061C48E /* DraftsModel.swift */,
4C54AA0629A540BA003E4487 /* NotificationsModel.swift */,
);
path = Models;
sourceTree = "<group>";
};
4C30AC7029A5676F00E2BD5A /* Notifications */ = {
isa = PBXGroup;
children = (
4C30AC7129A5677A00E2BD5A /* NotificationsView.swift */,
4C30AC7329A5680900E2BD5A /* EventGroupView.swift */,
4C30AC7529A5770900E2BD5A /* NotificationItemView.swift */,
4C30AC7F29A6A53F00E2BD5A /* ProfilePicturesView.swift */,
);
path = Notifications;
sourceTree = "<group>";
};
4C54AA0829A55416003E4487 /* Notifications */ = {
isa = PBXGroup;
children = (
4C54AA0929A55429003E4487 /* EventGroup.swift */,
4C54AA0B29A5543C003E4487 /* ZapGroup.swift */,
);
path = Notifications;
sourceTree = "<group>";
};
4C75EFA227FA576C0006080F /* Views */ = {
isa = PBXGroup;
children = (
4C30AC7029A5676F00E2BD5A /* Notifications */,
4CE0E2B029A3DF4700DB4CA2 /* Timeline */,
4CE879562996C44A00F758CC /* Zaps */,
4CB9D4A52992D01900A9A7E4 /* Profile */,
@ -729,6 +791,7 @@
4C363A8D28236FE4006E126D /* NoteContentView.swift */,
4C75EFAC28049CFB0006080F /* PostButton.swift */,
4C75EFA327FA577B0006080F /* PostView.swift */,
9C83F89229A937B900136C08 /* TextViewWrapper.swift */,
4CEE2AF8280B2EAC00AB5EEF /* PowView.swift */,
4CEE2AF6280B2DEA00AB5EEF /* ProfileName.swift */,
4C285C892838B985008A31F1 /* ProfilePictureSelector.swift */,
@ -812,6 +875,8 @@
4C2CDDF6299D4A5E00879FD5 /* Debouncer.swift */,
7C95CAED299DCEF1009DCB67 /* KFOptionSetter+.swift */,
4CE0E2AE29A2E82100DB4CA2 /* EventHolder.swift */,
3A3040F029A8FF97008A0F29 /* LocalizationUtil.swift */,
4C30AC7729A577AB00E2BD5A /* EventCache.swift */,
);
path = Util;
sourceTree = "<group>";
@ -853,6 +918,7 @@
children = (
4CB9D4A62992D02B00A9A7E4 /* ProfileNameView.swift */,
4CB9D4A82992D2F400A9A7E4 /* FollowsYou.swift */,
4C9F18E329ABDE6D008C55EC /* MaybeAnonPfpView.swift */,
);
path = Profile;
sourceTree = "<group>";
@ -974,6 +1040,10 @@
4CF0ABDB2981A19E00D66079 /* ListTests.swift */,
4CB883A9297612FF00DC99E7 /* ZapTests.swift */,
4CB883AD2976FA9300DC99E7 /* FormatTests.swift */,
3A3040EC29A5CB86008A0F29 /* ReplyDescriptionTests.swift */,
3A3040EE29A8FEE9008A0F29 /* EventDetailBarTests.swift */,
3A3040F229A91366008A0F29 /* ProfileViewTests.swift */,
3A30410029AB12AA008A0F29 /* EventGroupViewTests.swift */,
);
path = damusTests;
sourceTree = "<group>";
@ -1008,6 +1078,7 @@
isa = PBXGroup;
children = (
4CE879572996C45300F758CC /* ZapsView.swift */,
4C9F18E129AA9B6C008C55EC /* CustomizeZapView.swift */,
);
path = Zaps;
sourceTree = "<group>";
@ -1163,6 +1234,8 @@
id,
cs,
ru,
"zh-HK",
"zh-TW",
);
mainGroup = 4CE6DEDA27F7A08100C66700;
packageReferences = (
@ -1219,6 +1292,7 @@
4C3AC79D2833036D00E1F516 /* FollowingView.swift in Sources */,
4C363A8A28236B57006E126D /* MentionView.swift in Sources */,
4CE4F8CD281352B30009DFBB /* Notifications.swift in Sources */,
4C30AC7829A577AB00E2BD5A /* EventCache.swift in Sources */,
4C285C8428385690008A31F1 /* CreateAccountView.swift in Sources */,
4C216F34286F5ACD00040376 /* DMView.swift in Sources */,
4C3EA64428FF558100C48A62 /* sha256.c in Sources */,
@ -1231,6 +1305,7 @@
4CE8794C2995B59E00F758CC /* RelayMetadatas.swift in Sources */,
4C363A8C28236B92006E126D /* PubkeyView.swift in Sources */,
4C5C7E68284ED36500A22DF5 /* SearchHomeModel.swift in Sources */,
4C54AA0C29A5543C003E4487 /* ZapGroup.swift in Sources */,
4C75EFB728049D990006080F /* RelayPool.swift in Sources */,
4CF0ABEE29844B5500D66079 /* AnyEncodable.swift in Sources */,
4CB8838D296F710400DC99E7 /* Reposted.swift in Sources */,
@ -1242,6 +1317,7 @@
4C363AA228296A7E006E126D /* SearchView.swift in Sources */,
4CC7AAED297F0B9E00430951 /* Highlight.swift in Sources */,
4C285C8A2838B985008A31F1 /* ProfilePictureSelector.swift in Sources */,
4C9F18E429ABDE6D008C55EC /* MaybeAnonPfpView.swift in Sources */,
4C75EFB92804A2740006080F /* EventView.swift in Sources */,
3AA247FD297E3CFF0090C62D /* RepostsModel.swift in Sources */,
F75BA12F29A18EF500E10810 /* BookmarksView.swift in Sources */,
@ -1273,6 +1349,7 @@
4C649844285A952100EAE2B3 /* LocalUserConfig.swift in Sources */,
4C75EFB328049D640006080F /* NostrEvent.swift in Sources */,
4CA2EFA0280E37AC0044ACD8 /* TimelineView.swift in Sources */,
4C30AC7629A5770900E2BD5A /* NotificationItemView.swift in Sources */,
4C363A8428233689006E126D /* Parser.swift in Sources */,
3AAA95CA298DF87B00F3D526 /* TranslationService.swift in Sources */,
4CE4F9E328528C5200C00DD9 /* AddRelayView.swift in Sources */,
@ -1284,6 +1361,7 @@
E9E4ED0B295867B900DD7078 /* ThreadV2View.swift in Sources */,
4C3BEFDC281DCE6100B3DE84 /* Liked.swift in Sources */,
4CF0ABE7298444FD00D66079 /* MutedEventView.swift in Sources */,
9C83F89329A937B900136C08 /* TextViewWrapper.swift in Sources */,
4CF0ABE12981A83900D66079 /* MutelistView.swift in Sources */,
4CB883A82975FC1800DC99E7 /* Zaps.swift in Sources */,
4C75EFB128049D510006080F /* NostrResponse.swift in Sources */,
@ -1292,6 +1370,7 @@
31D2E847295218AF006D67F8 /* Shimmer.swift in Sources */,
F7908E97298B1FDF00AB113A /* NIPURLBuilder.swift in Sources */,
4C285C8228385570008A31F1 /* CarouselView.swift in Sources */,
3A3040F129A8FF97008A0F29 /* LocalizationUtil.swift in Sources */,
F75BA12D29A1855400E10810 /* BookmarksManager.swift in Sources */,
4C3EA67F28FFC01D00C48A62 /* InvoiceView.swift in Sources */,
4CE8794829941DA700F758CC /* RelayFilters.swift in Sources */,
@ -1321,16 +1400,19 @@
4C285C8C28398BC7008A31F1 /* Keys.swift in Sources */,
4CACA9DC280C38C000D9BBE8 /* Profiles.swift in Sources */,
4CE879582996C45300F758CC /* ZapsView.swift in Sources */,
4C30AC7429A5680900E2BD5A /* EventGroupView.swift in Sources */,
4C633352283D419F00B1C9C3 /* SignalModel.swift in Sources */,
9609F058296E220800069BF3 /* BannerImageView.swift in Sources */,
4C363A94282704FA006E126D /* Post.swift in Sources */,
4C216F32286E388800040376 /* DMChatView.swift in Sources */,
4CAAD8AD298851D000060CEA /* AccountDeletion.swift in Sources */,
4C54AA0A29A55429003E4487 /* EventGroup.swift in Sources */,
4C3EA67928FF7ABF00C48A62 /* list.c in Sources */,
4C64987E286D082C00EAE2B3 /* DirectMessagesModel.swift in Sources */,
4CE0E2B629A3ED5500DB4CA2 /* InnerTimelineView.swift in Sources */,
4C363A8828236948006E126D /* BlocksView.swift in Sources */,
4C06670628FCB08600038D2A /* ImageCarousel.swift in Sources */,
4C9F18E229AA9B6C008C55EC /* CustomizeZapView.swift in Sources */,
4C75EFAF28049D350006080F /* NostrFilter.swift in Sources */,
4C3EA64C28FF59AC00C48A62 /* bech32_util.c in Sources */,
4C42812C298C848200DBF26F /* TranslateView.swift in Sources */,
@ -1351,6 +1433,7 @@
4CF0ABD629817F5B00D66079 /* ReportView.swift in Sources */,
4CB8838629656C8B00DC99E7 /* NIP05.swift in Sources */,
4CF0ABD82981980C00D66079 /* Lists.swift in Sources */,
4C30AC8029A6A53F00E2BD5A /* ProfilePicturesView.swift in Sources */,
4C5C7E6A284EDE2E00A22DF5 /* SearchResultsView.swift in Sources */,
7C60CAEF298471A1009C80D6 /* CoreSVG.swift in Sources */,
6439E014296790CF0020672B /* ProfileZoomView.swift in Sources */,
@ -1380,6 +1463,7 @@
4C3AC79B28306D7B00E1F516 /* Contacts.swift in Sources */,
4C3EA63D28FF52D600C48A62 /* bolt11.c in Sources */,
7CFF6317299FEFE5005D382A /* SelectableText.swift in Sources */,
4C54AA0729A540BA003E4487 /* NotificationsModel.swift in Sources */,
4CB55EF3295E5D59007FD187 /* RecommendedRelayView.swift in Sources */,
4CF0ABEC29844B4700D66079 /* AnyDecodable.swift in Sources */,
4C5F9118283D88E40052CD1C /* FollowingModel.swift in Sources */,
@ -1404,6 +1488,7 @@
4C99737B28C92A9200E53835 /* ChatroomMetadata.swift in Sources */,
4CC7AAF4297F18B400430951 /* ReplyDescription.swift in Sources */,
4C75EFA427FA577B0006080F /* PostView.swift in Sources */,
4C30AC7229A5677A00E2BD5A /* NotificationsView.swift in Sources */,
4C75EFB528049D790006080F /* Relay.swift in Sources */,
4CEE2AF1280B216B00AB5EEF /* EventDetailView.swift in Sources */,
4CC7AAFA297F64AC00430951 /* EventMenu.swift in Sources */,
@ -1419,8 +1504,11 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
3A3040ED29A5CB86008A0F29 /* ReplyDescriptionTests.swift in Sources */,
3A30410129AB12AA008A0F29 /* EventGroupViewTests.swift in Sources */,
3ACBCB78295FE5C70037388A /* TimeAgoTests.swift in Sources */,
DD597CBD2963D85A00C64D32 /* MarkdownTests.swift in Sources */,
3A3040EF29A8FEE9008A0F29 /* EventDetailBarTests.swift in Sources */,
4C3EA67B28FF7B3900C48A62 /* InvoiceTests.swift in Sources */,
5023E76329AA3627007D3D50 /* RelayPoolTests.swift in Sources */,
4C363A9E2828A822006E126D /* ReplyTests.swift in Sources */,
@ -1431,6 +1519,7 @@
4C90BD1C283AC38E008EE7EF /* Bech32Tests.swift in Sources */,
50A50A8D29A09E1C00C01BE7 /* RequestTests.swift in Sources */,
4CE6DEF827F7A08200C66700 /* damusTests.swift in Sources */,
3A3040F329A91366008A0F29 /* ProfileViewTests.swift in Sources */,
4CF0ABDC2981A19E00D66079 /* ListTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -1480,6 +1569,8 @@
3A41E55B299D52BE001FA465 /* id */,
3A8624DB299E82BE00BD8BE9 /* cs */,
3A827A1A299FC69D00C4D171 /* ru */,
3A3040FB29A91F03008A0F29 /* zh-HK */,
3A3040FD29A91F31008A0F29 /* zh-TW */,
);
name = Localizable.stringsdict;
sourceTree = "<group>";
@ -1503,6 +1594,8 @@
3A41E559299D52BE001FA465 /* id */,
3A8624D9299E82BE00BD8BE9 /* cs */,
3A827A18299FC69D00C4D171 /* ru */,
3A3040F929A91ED6008A0F29 /* zh-HK */,
3A3040FC29A91F31008A0F29 /* zh-TW */,
);
name = InfoPlist.strings;
sourceTree = "<group>";
@ -1526,6 +1619,9 @@
3A41E55A299D52BE001FA465 /* id */,
3A8624DA299E82BE00BD8BE9 /* cs */,
3A827A19299FC69D00C4D171 /* ru */,
3A3040FA29A91EFC008A0F29 /* zh-HK */,
3A3040FE29A91F31008A0F29 /* zh-TW */,
3A3040FF29AB02D1008A0F29 /* en-US */,
);
name = Localizable.strings;
sourceTree = "<group>";
@ -1661,7 +1757,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 7;
CURRENT_PROJECT_VERSION = 9;
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
DEVELOPMENT_TEAM = XK7H4JAB3D;
ENABLE_PREVIEWS = YES;
@ -1703,7 +1799,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 7;
CURRENT_PROJECT_VERSION = 9;
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
DEVELOPMENT_TEAM = XK7H4JAB3D;
ENABLE_PREVIEWS = YES;

View File

@ -205,6 +205,7 @@ struct ImageCarousel: View {
view.framePreloadCount = 3
}
.aspectRatio(contentMode: .fit)
.cornerRadius(10)
.tabItem {
Text(url.absoluteString)
}
@ -217,11 +218,11 @@ struct ImageCarousel: View {
}
}
}
.cornerRadius(10)
.fullScreenCover(isPresented: $open_sheet) {
ImageView(urls: urls)
}
.frame(height: 200)
.clipped()
.onTapGesture {
open_sheet = true
}

View File

@ -12,11 +12,7 @@ struct UserView: View {
let pubkey: String
var body: some View {
let pmodel = ProfileModel(pubkey: pubkey, damus: damus_state)
let followers = FollowersModel(damus_state: damus_state, target: pubkey)
let pv = ProfileView(damus_state: damus_state, profile: pmodel, followers: followers)
NavigationLink(destination: pv) {
NavigationLink(destination: ProfileView(damus_state: damus_state, pubkey: pubkey)) {
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: .none, profiles: damus_state.profiles)
VStack(alignment: .leading) {

View File

@ -7,6 +7,22 @@
import SwiftUI
enum ZappingEventType {
case failed(ZappingError)
case got_zap_invoice(String)
}
enum ZappingError {
case fetching_invoice
case bad_lnurl
}
struct ZappingEvent {
let is_custom: Bool
let type: ZappingEventType
let event: NostrEvent
}
struct ZapButton: View {
let damus_state: DamusState
let event: NostrEvent
@ -19,61 +35,8 @@ struct ZapButton: View {
@State var slider_value: Double = 0.0
@State var slider_visible: Bool = false
@State var showing_select_wallet: Bool = false
func send_zap() {
guard let privkey = damus_state.keypair.privkey else {
return
}
// Only take the first 10 because reasons
let relays = Array(damus_state.pool.descriptors.prefix(10))
let target = ZapTarget.note(id: event.id, author: event.pubkey)
// TODO: gather comment?
let content = ""
let zapreq = make_zap_request_event(pubkey: damus_state.pubkey, privkey: privkey, content: content, relays: relays, target: target)
zapping = true
Task {
var mpayreq = damus_state.lnurls.lookup(target.pubkey)
if mpayreq == nil {
mpayreq = await fetch_static_payreq(lnurl)
}
guard let payreq = mpayreq else {
// TODO: show error
DispatchQueue.main.async {
zapping = false
}
return
}
DispatchQueue.main.async {
damus_state.lnurls.endpoints[target.pubkey] = payreq
}
let zap_amount = get_default_zap_amount(pubkey: damus_state.pubkey) ?? 1000
guard let inv = await fetch_zap_invoice(payreq, zapreq: zapreq, sats: zap_amount) else {
DispatchQueue.main.async {
zapping = false
}
return
}
DispatchQueue.main.async {
zapping = false
if should_show_wallet_selector(damus_state.pubkey) {
self.invoice = inv
self.showing_select_wallet = true
} else {
open_with_wallet(wallet: get_default_wallet(damus_state.pubkey).model, invoice: inv)
}
}
}
//damus_state.pool.send(.event(zapreq))
}
@State var showing_zap_customizer: Bool = false
@State var is_charging: Bool = false
var zap_img: String {
if bar.zapped {
@ -92,6 +55,10 @@ struct ZapButton: View {
return Color.orange
}
if is_charging {
return Color.yellow
}
if !zapping {
return nil
}
@ -101,22 +68,62 @@ struct ZapButton: View {
var body: some View {
HStack(spacing: 4) {
EventActionButton(img: zap_img, col: zap_color) {
if bar.zapped {
//notify(.delete, bar.our_tip)
} else if !zapping {
send_zap()
Image(systemName: zap_img)
.foregroundColor(zap_color == nil ? Color.gray : zap_color!)
.font(.footnote.weight(.medium))
.onTapGesture {
if bar.zapped {
//notify(.delete, bar.our_tip)
} else if !zapping {
self.showing_zap_customizer = true
//send_zap(damus_state: damus_state, event: event, lnurl: lnurl, is_custom: false)
//self.zapping = true
}
}
.onLongPressGesture(minimumDuration: 0, pressing: { is_charing in
self.is_charging = is_charging
}, perform: {
self.showing_zap_customizer = true
})
.accessibilityLabel(NSLocalizedString("Zap", comment: "Accessibility label for zap button"))
if bar.zap_total > 0 {
Text(verbatim: format_msats_abbrev(bar.zap_total))
.font(.footnote)
.foregroundColor(bar.zapped ? Color.orange : Color.gray)
}
.accessibilityLabel(NSLocalizedString("Zap", comment: "Accessibility label for zap button"))
Text(String("\(bar.zap_total > 0 ? "\(format_msats_abbrev(bar.zap_total))" : "")"))
.font(.footnote)
.foregroundColor(bar.zapped ? Color.orange : Color.gray)
}
.sheet(isPresented: $showing_zap_customizer) {
CustomizeZapView(state: damus_state, event: event, lnurl: lnurl)
}
.sheet(isPresented: $showing_select_wallet, onDismiss: {showing_select_wallet = false}) {
SelectWalletView(showingSelectWallet: $showing_select_wallet, our_pubkey: damus_state.pubkey, invoice: invoice)
}
.onReceive(handle_notify(.zapping)) { notif in
let zap_ev = notif.object as! ZappingEvent
guard zap_ev.event.id == self.event.id else {
return
}
guard !zap_ev.is_custom else {
return
}
switch zap_ev.type {
case .failed:
break
case .got_zap_invoice(let inv):
if should_show_wallet_selector(damus_state.pubkey) {
self.invoice = inv
self.showing_select_wallet = true
} else {
open_with_wallet(wallet: get_default_wallet(damus_state.pubkey).model, invoice: inv)
}
}
self.zapping = false
}
}
}
@ -128,3 +135,55 @@ struct ZapButton_Previews: PreviewProvider {
}
}
func send_zap(damus_state: DamusState, event: NostrEvent, lnurl: String, is_custom: Bool, comment: String?, amount_sats: Int?, zap_type: ZapType) {
guard let privkey = damus_state.keypair.privkey else {
return
}
// Only take the first 10 because reasons
let relays = Array(damus_state.pool.descriptors.prefix(10))
let target = ZapTarget.note(id: event.id, author: event.pubkey)
let content = comment ?? ""
let zapreq = make_zap_request_event(pubkey: damus_state.pubkey, privkey: privkey, content: content, relays: relays, target: target, is_anon: zap_type == .anon)
Task {
var mpayreq = damus_state.lnurls.lookup(target.pubkey)
if mpayreq == nil {
mpayreq = await fetch_static_payreq(lnurl)
}
guard let payreq = mpayreq else {
// TODO: show error
DispatchQueue.main.async {
let typ = ZappingEventType.failed(.bad_lnurl)
let ev = ZappingEvent(is_custom: is_custom, type: typ, event: event)
notify(.zapping, ev)
}
return
}
DispatchQueue.main.async {
damus_state.lnurls.endpoints[target.pubkey] = payreq
}
let zap_amount = amount_sats ?? get_default_zap_amount(pubkey: damus_state.pubkey) ?? 1000
guard let inv = await fetch_zap_invoice(payreq, zapreq: zapreq, sats: zap_amount, zap_type: zap_type, comment: comment) else {
DispatchQueue.main.async {
let typ = ZappingEventType.failed(.fetching_invoice)
let ev = ZappingEvent(is_custom: is_custom, type: typ, event: event)
notify(.zapping, ev)
}
return
}
DispatchQueue.main.async {
let ev = ZappingEvent(is_custom: is_custom, type: .got_zap_invoice(inv), event: event)
notify(.zapping, ev)
}
}
return
}

View File

@ -166,7 +166,7 @@ struct ContentView: View {
Text("Universe 🛸", comment: "Toolbar label for the universal view where posts from all connected relay servers appear.")
.bold()
case .none:
Text("", comment: "Toolbar label for unknown views. This label would be displayed only if a new timeline view is added but a toolbar label was not explicitly assigned to it yet.")
Text(verbatim: "")
}
}
}
@ -192,7 +192,7 @@ struct ContentView: View {
case .notifications:
VStack(spacing: 0) {
Divider()
TimelineView(events: home.notifications, loading: $home.loading, damus: damus, show_friend_icon: true, filter: { _ in true })
NotificationsView(state: damus, notifications: home.notifications)
}
case .dms:
DirectMessagesView(damus_state: damus_state!)
@ -615,7 +615,8 @@ struct ContentView: View {
settings: UserSettingsStore(),
relay_filters: relay_filters,
relay_metadata: metadatas,
drafts: Drafts()
drafts: Drafts(),
events: EventCache()
)
home.damus_state = self.damus_state!

View File

@ -24,6 +24,7 @@ struct DamusState {
let relay_filters: RelayFilters
let relay_metadata: RelayMetadatas
let drafts: Drafts
let events: EventCache
var pubkey: String {
return keypair.pubkey
@ -32,9 +33,8 @@ struct DamusState {
var is_privkey_user: Bool {
keypair.privkey != nil
}
static var empty: DamusState {
return DamusState.init(pool: RelayPool(), keypair: Keypair(pubkey: "", privkey: ""), likes: EventCounter(our_pubkey: ""), boosts: EventCounter(our_pubkey: ""), contacts: Contacts(our_pubkey: ""), tips: TipCounter(our_pubkey: ""), profiles: Profiles(), dms: DirectMessagesModel(our_pubkey: ""), previews: PreviewCache(), zaps: Zaps(our_pubkey: ""), lnurls: LNUrls(), settings: UserSettingsStore(), relay_filters: RelayFilters(our_pubkey: ""), relay_metadata: RelayMetadatas(), drafts: Drafts())
return DamusState.init(pool: RelayPool(), keypair: Keypair(pubkey: "", privkey: ""), likes: EventCounter(our_pubkey: ""), boosts: EventCounter(our_pubkey: ""), contacts: Contacts(our_pubkey: ""), tips: TipCounter(our_pubkey: ""), profiles: Profiles(), dms: DirectMessagesModel(our_pubkey: ""), previews: PreviewCache(), zaps: Zaps(our_pubkey: ""), lnurls: LNUrls(), settings: UserSettingsStore(), relay_filters: RelayFilters(our_pubkey: ""), relay_metadata: RelayMetadatas(), drafts: Drafts(), events: EventCache())
}
}

View File

@ -8,6 +8,6 @@
import Foundation
class Drafts: ObservableObject {
@Published var post: String = ""
@Published var replies: [NostrEvent: String] = [:]
@Published var post: NSMutableAttributedString = NSMutableAttributedString(string: "")
@Published var replies: [NostrEvent: NSMutableAttributedString] = [:]
}

View File

@ -65,7 +65,7 @@ class EventsModel: ObservableObject {
case .notice(_):
break
case .eose(_):
load_profiles(profiles_subid: profiles_id, relay_id: relay_id, events: events, damus_state: state)
load_profiles(profiles_subid: profiles_id, relay_id: relay_id, load: .from_events(events), damus_state: state)
}
}
}

View File

@ -50,22 +50,18 @@ class HomeModel: ObservableObject {
let profiles_subid = UUID().description
@Published var new_events: NewEventsBits = NewEventsBits()
@Published var notifications: EventHolder
@Published var notifications = NotificationsModel()
@Published var dms: DirectMessagesModel
@Published var events: EventHolder
@Published var events = EventHolder()
@Published var loading: Bool = false
@Published var signal: SignalModel = SignalModel()
init() {
self.events = EventHolder()
self.notifications = EventHolder()
self.damus_state = DamusState.empty
self.dms = DirectMessagesModel(our_pubkey: "")
}
init(damus_state: DamusState) {
self.events = EventHolder()
self.notifications = EventHolder()
self.damus_state = damus_state
self.dms = DirectMessagesModel(our_pubkey: damus_state.pubkey)
self.setup_debouncer()
@ -129,6 +125,8 @@ class HomeModel: ObservableObject {
handle_channel_meta(ev)
case .zap:
handle_zap_event(ev)
case .zap_request:
break
}
}
@ -143,7 +141,7 @@ class HomeModel: ObservableObject {
return
}
if !notifications.insert(ev) {
if !notifications.insert_zap(zap) {
return
}
@ -229,7 +227,7 @@ class HomeModel: ObservableObject {
guard inner_ev.is_valid else {
return
}
if inner_ev.is_textlike {
handle_text_event(sub_id: sub_id, ev)
}
@ -255,12 +253,11 @@ class HomeModel: ObservableObject {
return
}
// CHECK SIGS ON THESE
switch damus_state.likes.add_event(ev, target: e.ref_id) {
case .already_counted:
break
case .success(let n):
handle_notification(ev: ev)
let liked = Counted(event: ev, id: e.ref_id, total: n)
notify(.liked, liked)
notify(.update_stats, e.ref_id)
@ -320,9 +317,9 @@ class HomeModel: ObservableObject {
if sub_id == dms_subid {
var dms = dms.dms.flatMap { $0.1.events }
dms.append(contentsOf: incoming_dms)
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, events: dms, damus_state: damus_state)
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, load: .from_events(dms), damus_state: damus_state)
} else if sub_id == notifications_subid {
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, events: notifications.all_events, damus_state: damus_state)
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, load: .from_keys(notifications.uniq_pubkeys()), damus_state: damus_state)
}
self.loading = false
@ -375,7 +372,6 @@ class HomeModel: ObservableObject {
// TODO: separate likes?
var home_filter = NostrFilter.filter_kinds([
NostrKind.text.rawValue,
NostrKind.chat.rawValue,
NostrKind.like.rawValue,
NostrKind.boost.rawValue,
])
@ -385,7 +381,6 @@ class HomeModel: ObservableObject {
var notifications_filter = NostrFilter.filter_kinds([
NostrKind.text.rawValue,
NostrKind.chat.rawValue,
NostrKind.like.rawValue,
NostrKind.boost.rawValue,
NostrKind.zap.rawValue,
@ -461,7 +456,16 @@ class HomeModel: ObservableObject {
return
}
if !notifications.insert(ev) {
guard should_show_event(contacts: damus_state.contacts, ev: ev) else {
return
}
damus_state.events.insert(ev)
if let inner_ev = ev.inner_event {
damus_state.events.insert(inner_ev)
}
if !notifications.insert_event(ev) {
return
}
@ -484,6 +488,8 @@ class HomeModel: ObservableObject {
guard should_show_event(contacts: damus_state.contacts, ev: ev) else {
return
}
damus_state.events.insert(ev)
if sub_id == home_subid {
insert_home_event(ev)

View File

@ -263,17 +263,19 @@ func format_msats_abbrev(_ msats: Int64) -> String {
return formatter.string(from: sats) ?? sats.stringValue
}
func format_msats(_ msat: Int64) -> String {
func format_msats(_ msat: Int64, locale: Locale = Locale.current) -> String {
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
numberFormatter.minimumFractionDigits = 0
numberFormatter.maximumFractionDigits = 3
numberFormatter.roundingMode = .down
numberFormatter.locale = locale
let sats = NSNumber(value: (Double(msat) / 1000.0))
let formattedSats = numberFormatter.string(from: sats) ?? sats.stringValue
return String(format: Bundle.main.localizedString(forKey: "sats_count", value: nil, table: nil), sats.decimalValue as NSDecimalNumber, formattedSats)
let bundle = bundleForLocale(locale: locale)
return String(format: bundle.localizedString(forKey: "sats_count", value: nil, table: nil), locale: locale, sats.decimalValue as NSDecimalNumber, formattedSats)
}
func convert_invoice_block(_ b: invoice_block) -> Block? {

View File

@ -0,0 +1,32 @@
//
// ReactionGroup.swift
// damus
//
// Created by William Casarin on 2023-02-21.
//
import Foundation
class EventGroup {
var events: [NostrEvent]
var last_event_at: Int64 {
guard let first = self.events.first else {
return 0
}
return first.created_at
}
init() {
self.events = []
}
init(events: [NostrEvent]) {
self.events = events
}
func insert(_ ev: NostrEvent) -> Bool {
return insert_uniq_sorted_event_created(events: &events, new_ev: ev)
}
}

View File

@ -0,0 +1,53 @@
//
// ZapGroup.swift
// damus
//
// Created by William Casarin on 2023-02-21.
//
import Foundation
class ZapGroup {
var zaps: [Zap]
var msat_total: Int64
var zappers: Set<String>
var last_event_at: Int64 {
guard let first = zaps.first else {
return 0
}
return first.event.created_at
}
func zap_requests() -> [NostrEvent] {
zaps.map { z in z.request.ev }
}
init(zaps: [Zap]) {
self.zaps = zaps
self.msat_total = 0
self.zappers = Set()
}
init() {
self.zaps = []
self.msat_total = 0
self.zappers = Set()
}
func insert(_ zap: Zap) -> Bool {
if !insert_uniq_sorted_zap_by_created(zaps: &zaps, new_zap: zap) {
return false
}
msat_total += zap.invoice.amount
if !zappers.contains(zap.request.ev.pubkey) {
zappers.insert(zap.request.ev.pubkey)
}
return true
}
}

View File

@ -0,0 +1,298 @@
//
// NotificationsModel.swift
// damus
//
// Created by William Casarin on 2023-02-21.
//
import Foundation
enum NotificationItem {
case repost(String, EventGroup)
case reaction(String, EventGroup)
case profile_zap(ZapGroup)
case event_zap(String, ZapGroup)
case reply(NostrEvent)
var id: String {
switch self {
case .repost(let evid, _):
return "repost_" + evid
case .reaction(let evid, _):
return "reaction_" + evid
case .profile_zap:
return "profile_zap"
case .event_zap(let evid, _):
return "event_zap_" + evid
case .reply(let ev):
return "reply_" + ev.id
}
}
var last_event_at: Int64 {
switch self {
case .reaction(_, let evgrp):
return evgrp.last_event_at
case .repost(_, let evgrp):
return evgrp.last_event_at
case .profile_zap(let zapgrp):
return zapgrp.last_event_at
case .event_zap(_, let zapgrp):
return zapgrp.last_event_at
case .reply(let reply):
return reply.created_at
}
}
}
class NotificationsModel: ObservableObject, ScrollQueue {
var incoming_zaps: [Zap]
var incoming_events: [NostrEvent]
var should_queue: Bool
// mappings from events to
var zaps: [String: ZapGroup]
var profile_zaps: ZapGroup
var reactions: [String: EventGroup]
var reposts: [String: EventGroup]
var replies: [NostrEvent]
var has_reply: Set<String>
@Published var notifications: [NotificationItem]
init() {
self.zaps = [:]
self.reactions = [:]
self.reposts = [:]
self.replies = []
self.has_reply = Set()
self.should_queue = true
self.incoming_zaps = []
self.incoming_events = []
self.profile_zaps = ZapGroup()
self.notifications = []
}
func set_should_queue(_ val: Bool) {
self.should_queue = val
}
func uniq_pubkeys() -> [String] {
var pks = Set<String>()
for ev in incoming_events {
pks.insert(ev.pubkey)
}
for grp in reposts {
for ev in grp.value.events {
pks.insert(ev.pubkey)
}
}
for ev in replies {
pks.insert(ev.pubkey)
}
for zap in incoming_zaps {
pks.insert(zap.request.ev.pubkey)
}
return Array(pks)
}
func build_notifications() -> [NotificationItem] {
var notifs: [NotificationItem] = []
for el in zaps {
let evid = el.key
let zapgrp = el.value
let notif: NotificationItem = .event_zap(evid, zapgrp)
notifs.append(notif)
}
if !profile_zaps.zaps.isEmpty {
notifs.append(.profile_zap(profile_zaps))
}
for el in reposts {
let evid = el.key
let evgrp = el.value
notifs.append(.repost(evid, evgrp))
}
for el in reactions {
let evid = el.key
let evgrp = el.value
notifs.append(.reaction(evid, evgrp))
}
for reply in replies {
notifs.append(.reply(reply))
}
notifs.sort { $0.last_event_at > $1.last_event_at }
return notifs
}
private func insert_repost(_ ev: NostrEvent) -> Bool {
guard let reposted_ev = ev.inner_event else {
return false
}
let id = reposted_ev.id
if let evgrp = self.reposts[id] {
return evgrp.insert(ev)
} else {
let evgrp = EventGroup()
self.reposts[id] = evgrp
return evgrp.insert(ev)
}
}
private func insert_text(_ ev: NostrEvent) -> Bool {
guard !has_reply.contains(ev.id) else {
return false
}
has_reply.insert(ev.id)
replies.append(ev)
return true
}
private func insert_reaction(_ ev: NostrEvent) -> Bool {
guard let ref_id = ev.referenced_ids.last else {
return false
}
let id = ref_id.id
if let evgrp = self.reactions[id] {
return evgrp.insert(ev)
} else {
let evgrp = EventGroup()
self.reactions[id] = evgrp
return evgrp.insert(ev)
}
}
private func insert_event_immediate(_ ev: NostrEvent) -> Bool {
if ev.known_kind == .boost {
return insert_repost(ev)
} else if ev.known_kind == .like {
return insert_reaction(ev)
} else if ev.known_kind == .text {
return insert_text(ev)
}
return false
}
private func insert_zap_immediate(_ zap: Zap) -> Bool {
switch zap.target {
case .note(let notezt):
let id = notezt.note_id
if let zapgrp = self.zaps[notezt.note_id] {
return zapgrp.insert(zap)
} else {
let zapgrp = ZapGroup()
self.zaps[id] = zapgrp
return zapgrp.insert(zap)
}
case .profile:
return profile_zaps.insert(zap)
}
}
func insert_event(_ ev: NostrEvent) -> Bool {
if should_queue {
return insert_uniq_sorted_event_created(events: &incoming_events, new_ev: ev)
}
if insert_event_immediate(ev) {
self.notifications = build_notifications()
return true
}
return false
}
func insert_zap(_ zap: Zap) -> Bool {
if should_queue {
return insert_uniq_sorted_zap_by_created(zaps: &incoming_zaps, new_zap: zap)
}
if insert_zap_immediate(zap) {
self.notifications = build_notifications()
return true
}
return false
}
func filter(_ isIncluded: (NostrEvent) -> Bool) {
var changed = false
var count = 0
count = incoming_events.count
incoming_events = incoming_events.filter(isIncluded)
changed = changed || incoming_events.count != count
count = profile_zaps.zaps.count
profile_zaps.zaps = profile_zaps.zaps.filter { zap in isIncluded(zap.request.ev) }
changed = changed || profile_zaps.zaps.count != count
for el in reactions {
count = el.value.events.count
el.value.events = el.value.events.filter(isIncluded)
changed = changed || el.value.events.count != count
}
for el in reposts {
count = el.value.events.count
el.value.events = el.value.events.filter(isIncluded)
changed = changed || el.value.events.count != count
}
for el in zaps {
count = el.value.zaps.count
el.value.zaps = el.value.zaps.filter {
isIncluded($0.request.ev)
}
changed = changed || el.value.zaps.count != count
}
count = replies.count
replies = replies.filter(isIncluded)
changed = changed || replies.count != count
if changed {
self.notifications = build_notifications()
}
}
func flush() -> Bool {
var inserted = false
for zap in incoming_zaps {
inserted = insert_zap_immediate(zap) || inserted
}
for event in incoming_events {
inserted = insert_event_immediate(event) || inserted
}
if inserted {
self.notifications = build_notifications()
}
return inserted
}
}

View File

@ -76,7 +76,7 @@ class SearchHomeModel: ObservableObject {
// global events are not realtime
unsubscribe(to: relay_id)
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, events: events.all_events, damus_state: damus_state)
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, load: .from_events(events.all_events), damus_state: damus_state)
}
@ -98,8 +98,31 @@ func find_profiles_to_fetch_pk(profiles: Profiles, event_pubkeys: [String]) -> [
return Array(pubkeys)
}
func find_profiles_to_fetch(profiles: Profiles, load: PubkeysToLoad) -> [String] {
switch load {
case .from_events(let events):
return find_profiles_to_fetch_from_events(profiles: profiles, events: events)
case .from_keys(let pks):
return find_profiles_to_fetch_from_keys(profiles: profiles, pks: pks)
}
}
func find_profiles_to_fetch_from_keys(profiles: Profiles, pks: [String]) -> [String] {
var pubkeys = Set<String>()
func find_profiles_to_fetch(profiles: Profiles, events: [NostrEvent]) -> [String] {
for pk in pks {
if profiles.lookup(id: pk) != nil {
continue
}
pubkeys.insert(pk)
}
return Array(pubkeys)
}
func find_profiles_to_fetch_from_events(profiles: Profiles, events: [NostrEvent]) -> [String] {
var pubkeys = Set<String>()
for ev in events {
@ -113,9 +136,14 @@ func find_profiles_to_fetch(profiles: Profiles, events: [NostrEvent]) -> [String
return Array(pubkeys)
}
func load_profiles(profiles_subid: String, relay_id: String, events: [NostrEvent], damus_state: DamusState) {
enum PubkeysToLoad {
case from_events([NostrEvent])
case from_keys([String])
}
func load_profiles(profiles_subid: String, relay_id: String, load: PubkeysToLoad, damus_state: DamusState) {
var filter = NostrFilter.filter_profiles
let authors = find_profiles_to_fetch(profiles: damus_state.profiles, events: events)
let authors = find_profiles_to_fetch(profiles: damus_state.profiles, load: load)
filter.authors = authors
guard !authors.isEmpty else {

View File

@ -207,7 +207,7 @@ class ThreadModel: ObservableObject {
}
if sub_id == self.base_subid {
load_profiles(profiles_subid: self.profiles_subid, relay_id: relay_id, events: events, damus_state: damus_state)
load_profiles(profiles_subid: self.profiles_subid, relay_id: relay_id, load: .from_events(events), damus_state: damus_state)
}
}

View File

@ -50,14 +50,14 @@ class ZapsModel: ObservableObject {
break
case .eose:
let events = self.zaps.map { $0.request.ev }
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, events: events, damus_state: state)
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, load: .from_events(events), damus_state: state)
case .event(_, let ev):
guard ev.kind == 9735 else {
return
}
if let zap = state.zaps.zaps[ev.id] {
if insert_uniq_sorted_zap(zaps: &zaps, new_zap: zap) {
if insert_uniq_sorted_zap_by_amount(zaps: &zaps, new_zap: zap) {
objectWillChange.send()
}
} else {
@ -71,7 +71,7 @@ class ZapsModel: ObservableObject {
state.zaps.add_zap(zap: zap)
if insert_uniq_sorted_zap(zaps: &zaps, new_zap: zap) {
if insert_uniq_sorted_zap_by_amount(zaps: &zaps, new_zap: zap) {
objectWillChange.send()
}
}

View File

@ -141,6 +141,9 @@ struct Profile: Codable {
}
static func displayName(profile: Profile?, pubkey: String) -> String {
if pubkey == "anon" {
return "Anonymous"
}
let pk = bech32_nopre_pubkey(pubkey) ?? pubkey
return profile?.name ?? abbrev_pubkey(pk)
}

View File

@ -168,6 +168,9 @@ class NostrEvent: Codable, Identifiable, CustomStringConvertible, Equatable, Has
return decrypted(privkey: privkey) ?? "*failed to decrypt content*"
}
return content
/*
switch validity {
case .ok:
return content
@ -176,6 +179,7 @@ class NostrEvent: Codable, Identifiable, CustomStringConvertible, Equatable, Has
case .bad_sig:
return content + "\n\n*WARNING: invalid signature, could be forged!*"
}
*/
}
var description: String {
@ -573,14 +577,25 @@ func zap_target_to_tags(_ target: ZapTarget) -> [[String]] {
}
}
func make_zap_request_event(pubkey: String, privkey: String, content: String, relays: [RelayDescriptor], target: ZapTarget) -> NostrEvent {
func make_zap_request_event(pubkey: String, privkey: String, content: String, relays: [RelayDescriptor], target: ZapTarget, is_anon: Bool) -> NostrEvent {
var tags = zap_target_to_tags(target)
var relay_tag = ["relays"]
relay_tag.append(contentsOf: relays.map { $0.url.absoluteString })
tags.append(relay_tag)
let ev = NostrEvent(content: content, pubkey: pubkey, kind: 9734, tags: tags)
var priv = privkey
var pub = pubkey
if is_anon {
tags.append(["anon"])
let kp = generate_new_keypair()
pub = kp.pubkey
priv = kp.privkey!
}
let ev = NostrEvent(content: content, pubkey: pub, kind: 9734, tags: tags)
ev.id = calculate_event_id(ev: ev)
ev.sig = sign_event(privkey: privkey, ev: ev)
ev.sig = sign_event(privkey: priv, ev: ev)
return ev
}
@ -835,7 +850,7 @@ func first_eref_mention(ev: NostrEvent, privkey: String?) -> Mention? {
extension [ReferencedId] {
var pRefs: [ReferencedId] {
get {
self.filter { ref in
Set(self).filter { ref in
ref.key == "p"
}
}

View File

@ -21,4 +21,5 @@ enum NostrKind: Int {
case chat = 42
case list = 30000
case zap = 9735
case zap_request = 9734
}

View File

@ -0,0 +1,27 @@
//
// EventCache.swift
// damus
//
// Created by William Casarin on 2023-02-21.
//
import Foundation
class EventCache {
private var events: [String: NostrEvent]
func lookup(_ evid: String) -> NostrEvent? {
return events[evid]
}
func insert(_ ev: NostrEvent) {
guard events[ev.id] == nil else {
return
}
events[ev.id] = ev
}
init() {
self.events = [:]
}
}

View File

@ -8,11 +8,15 @@
import Foundation
/// Used for holding back events until they're ready to be displayed
class EventHolder: ObservableObject {
class EventHolder: ObservableObject, ScrollQueue {
private var has_event: Set<String>
@Published var events: [NostrEvent]
@Published var incoming: [NostrEvent]
@Published var should_queue: Bool
var should_queue: Bool
func set_should_queue(_ val: Bool) {
self.should_queue = val
}
var queued: Int {
return incoming.count

View File

@ -38,8 +38,7 @@ func insert_uniq_by_pubkey(events: inout [NostrEvent], new_ev: NostrEvent, cmp:
return true
}
@discardableResult
func insert_uniq_sorted_zap(zaps: inout [Zap], new_zap: Zap) -> Bool {
func insert_uniq_sorted_zap(zaps: inout [Zap], new_zap: Zap, cmp: (Zap, Zap) -> Bool) -> Bool {
var i: Int = 0
for zap in zaps {
@ -48,7 +47,7 @@ func insert_uniq_sorted_zap(zaps: inout [Zap], new_zap: Zap) -> Bool {
return false
}
if new_zap.invoice.amount > zap.invoice.amount {
if cmp(new_zap, zap) {
zaps.insert(new_zap, at: i)
return true
}
@ -59,6 +58,19 @@ func insert_uniq_sorted_zap(zaps: inout [Zap], new_zap: Zap) -> Bool {
return true
}
@discardableResult
func insert_uniq_sorted_zap_by_created(zaps: inout [Zap], new_zap: Zap) -> Bool {
return insert_uniq_sorted_zap(zaps: &zaps, new_zap: new_zap) { (a, b) in
a.event.created_at > b.event.created_at
}
}
@discardableResult
func insert_uniq_sorted_zap_by_amount(zaps: inout [Zap], new_zap: Zap) -> Bool {
return insert_uniq_sorted_zap(zaps: &zaps, new_zap: new_zap) { (a, b) in
a.invoice.amount > b.invoice.amount
}
}
func insert_uniq_sorted_event_created(events: inout [NostrEvent], new_ev: NostrEvent) -> Bool {
return insert_uniq_sorted_event(events: &events, new_ev: new_ev) {

View File

@ -9,8 +9,10 @@ import Foundation
struct LNUrlPayRequest: Decodable {
let allowsNostr: Bool?
let commentAllowed: Int?
let nostrPubkey: String?
let metadata: String?
let minSendable: Int64?
let maxSendable: Int64?
let status: String?

View File

@ -0,0 +1,17 @@
//
// LocalizationUtil.swift
// damus
//
// Created by Terry Yiu on 2/24/23.
//
import Foundation
func bundleForLocale(locale: Locale?) -> Bundle {
if locale == nil {
return Bundle.main
}
let path = Bundle.main.path(forResource: locale!.identifier, ofType: "lproj")
return path != nil ? (Bundle(path: path!) ?? Bundle.main) : Bundle.main
}

View File

@ -104,6 +104,9 @@ extension Notification.Name {
static var update_bookmarks: Notification.Name {
return Notification.Name("update_bookmarks")
}
static var zapping: Notification.Name {
return Notification.Name("zapping")
}
}
func handle_notify(_ name: Notification.Name) -> NotificationCenter.Publisher {

View File

@ -50,5 +50,6 @@ public func time_ago_since(_ date: Date, _ calendar: Calendar = Calendar.current
return formatter.string(from: DateComponents(calendar: calendar, second: second))!
}
return NSLocalizedString("now", comment: "String indicating that a given timestamp just occurred")
let bundle = bundleForLocale(locale: calendar.locale ?? Locale.current)
return NSLocalizedString("now", bundle: bundle, comment: "String indicating that a given timestamp just occurred")
}

View File

@ -285,7 +285,7 @@ func fetch_static_payreq(_ lnurl: String) async -> LNUrlPayRequest? {
return endpoint
}
func fetch_zap_invoice(_ payreq: LNUrlPayRequest, zapreq: NostrEvent, sats: Int) async -> String? {
func fetch_zap_invoice(_ payreq: LNUrlPayRequest, zapreq: NostrEvent, sats: Int, zap_type: ZapType, comment: String?) async -> String? {
guard var base_url = payreq.callback.flatMap({ URLComponents(string: $0) }) else {
return nil
}
@ -295,12 +295,18 @@ func fetch_zap_invoice(_ payreq: LNUrlPayRequest, zapreq: NostrEvent, sats: Int)
var query = [URLQueryItem(name: "amount", value: "\(amount)")]
if zappable {
if zappable && zap_type != .non_zap {
if let json = encode_json(zapreq) {
print("zapreq json: \(json)")
query.append(URLQueryItem(name: "nostr", value: json))
}
}
// add a lud12 comment as well if we have it
if let comment, let limit = payreq.commentAllowed, limit != 0 {
let limited_comment = String(comment.prefix(limit))
query.append(URLQueryItem(name: "comment", value: limited_comment))
}
base_url.queryItems = query

View File

@ -36,7 +36,7 @@ class Zaps {
if our_zaps[note_target.note_id] == nil {
our_zaps[note_target.note_id] = [zap]
} else {
insert_uniq_sorted_zap(zaps: &(our_zaps[note_target.note_id]!), new_zap: zap)
insert_uniq_sorted_zap_by_amount(zaps: &(our_zaps[note_target.note_id]!), new_zap: zap)
}
case .profile(_):
break

View File

@ -26,14 +26,16 @@ struct EventDetailBar: View {
HStack {
if bar.boosts > 0 {
NavigationLink(destination: RepostsView(damus_state: state, model: RepostsModel(state: state, target: target))) {
Text("\(Text(verbatim: "\(bar.boosts)").font(.body.bold())) \(Text(String(format: Bundle.main.localizedString(forKey: "reposts_count", value: nil, table: nil), bar.boosts)).foregroundColor(.gray))", comment: "Sentence composed of 2 variables to describe how many reposts. In source English, the first variable is the number of reposts, and the second variable is 'Repost' or 'Reposts'.")
let noun = Text(verbatim: "\(repostsCountString(bar.boosts))").foregroundColor(.gray)
Text("\(Text("\(bar.boosts)").font(.body.bold())) \(noun)", comment: "Sentence composed of 2 variables to describe how many reposts. In source English, the first variable is the number of reposts, and the second variable is 'Repost' or 'Reposts'.")
}
.buttonStyle(PlainButtonStyle())
}
if bar.likes > 0 {
NavigationLink(destination: ReactionsView(damus_state: state, model: ReactionsModel(state: state, target: target))) {
Text("\(Text(verbatim: "\(bar.likes)").font(.body.bold())) \(Text(String(format: Bundle.main.localizedString(forKey: "reactions_count", value: nil, table: nil), bar.likes)).foregroundColor(.gray))", comment: "Sentence composed of 2 variables to describe how many reactions there are on a post. In source English, the first variable is the number of reactions, and the second variable is 'Reaction' or 'Reactions'.")
let noun = Text(verbatim: "\(reactionsCountString(bar.likes))").foregroundColor(.gray)
Text("\(Text("\(bar.likes)").font(.body.bold())) \(noun)", comment: "Sentence composed of 2 variables to describe how many reactions there are on a post. In source English, the first variable is the number of reactions, and the second variable is 'Reaction' or 'Reactions'.")
}
.buttonStyle(PlainButtonStyle())
}
@ -41,7 +43,8 @@ struct EventDetailBar: View {
if bar.zaps > 0 {
let dst = ZapsView(state: state, target: .note(id: target, author: target_pk))
NavigationLink(destination: dst) {
Text("\(Text(verbatim: "\(bar.zaps)").font(.body.bold())) \(Text(String(format: Bundle.main.localizedString(forKey: "zaps_count", value: nil, table: nil), bar.zaps)).foregroundColor(.gray))", comment: "Sentence composed of 2 variables to describe how many zap payments there are on a post. In source English, the first variable is the number of zap payments, and the second variable is 'Zap' or 'Zaps'.")
let noun = Text(verbatim: "\(zapsCountString(bar.zaps))").foregroundColor(.gray)
Text("\(Text("\(bar.zaps)").font(.body.bold())) \(noun)", comment: "Sentence composed of 2 variables to describe how many zap payments there are on a post. In source English, the first variable is the number of zap payments, and the second variable is 'Zap' or 'Zaps'.")
}
.buttonStyle(PlainButtonStyle())
}
@ -49,6 +52,21 @@ struct EventDetailBar: View {
}
}
func repostsCountString(_ count: Int, locale: Locale = Locale.current) -> String {
let bundle = bundleForLocale(locale: locale)
return String(format: bundle.localizedString(forKey: "reposts_count", value: nil, table: nil), locale: locale, count)
}
func reactionsCountString(_ count: Int, locale: Locale = Locale.current) -> String {
let bundle = bundleForLocale(locale: locale)
return String(format: bundle.localizedString(forKey: "reactions_count", value: nil, table: nil), locale: locale, count)
}
func zapsCountString(_ count: Int, locale: Locale = Locale.current) -> String {
let bundle = bundleForLocale(locale: locale)
return String(format: bundle.localizedString(forKey: "zaps_count", value: nil, table: nil), locale: locale, count)
}
struct EventDetailBar_Previews: PreviewProvider {
static var previews: some View {
EventDetailBar(state: test_damus_state(), target: "", target_pk: "")

View File

@ -129,26 +129,14 @@ struct ConfigView: View {
}
}
Section(NSLocalizedString("Default Zap Amount in sats", comment: "Section title for zap configuration")) {
TextField(String("1000"), text: $default_zap_amount)
.keyboardType(.numberPad)
.onReceive(Just(default_zap_amount)) { newValue in
let filtered = newValue.filter { Set("0123456789").contains($0) }
if filtered != newValue {
default_zap_amount = filtered
if let parsed = handle_string_amount(new_value: newValue) {
self.default_zap_amount = String(parsed)
}
if filtered == "" {
set_default_zap_amount(pubkey: state.pubkey, amount: 1000)
return
}
guard let amt = Int(filtered) else {
return
}
set_default_zap_amount(pubkey: state.pubkey, amount: amt)
}
}
@ -220,10 +208,10 @@ struct ConfigView: View {
}
}
let bundleShortVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as! String
let bundleVersion = Bundle.main.infoDictionary?["CFBundleVersion"] as! String
Section(NSLocalizedString("Version", comment: "Section title for displaying the version number of the Damus app.")) {
Text(verbatim: "\(bundleShortVersion) (\(bundleVersion))")
if let bundleShortVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"], let bundleVersion = Bundle.main.infoDictionary?["CFBundleVersion"] {
Section(NSLocalizedString("Version", comment: "Section title for displaying the version number of the Damus app.")) {
Text(verbatim: "\(bundleShortVersion) (\(bundleVersion))")
}
}
}
}
@ -346,3 +334,18 @@ struct ConfigView_Previews: PreviewProvider {
}
}
}
func handle_string_amount(new_value: String) -> Int? {
let filtered = new_value.filter { Set("0123456789").contains($0) }
if filtered == "" {
return nil
}
guard let amt = Int(filtered) else {
return nil
}
return amt
}

View File

@ -37,9 +37,7 @@ struct DMChatView: View {
var Header: some View {
let profile = damus_state.profiles.lookup(id: pubkey)
let pmodel = ProfileModel(pubkey: pubkey, damus: damus_state)
let fmodel = FollowersModel(damus_state: damus_state, target: pubkey)
let profile_page = ProfileView(damus_state: damus_state, profile: pmodel, followers: fmodel)
let profile_page = ProfileView(damus_state: damus_state, pubkey: pubkey)
return NavigationLink(destination: profile_page) {
HStack {
ProfilePicView(pubkey: pubkey, size: 24, highlight: .none, profiles: damus_state.profiles)

View File

@ -41,6 +41,9 @@ struct DirectMessagesView: View {
ForEach(dms, id: \.0) { tup in
MaybeEvent(tup)
.padding(.top, 10)
Divider()
.padding([.top], 10)
}
}
}

View File

@ -23,7 +23,7 @@ func scroll_after_load(thread: ThreadModel, proxy: ScrollViewProxy) {
struct EventDetailView_Previews: PreviewProvider {
static var previews: some View {
let state = test_damus_state()
let _ = test_damus_state()
EventDetailView()
}
}

View File

@ -61,10 +61,8 @@ struct EventView: View {
if event.known_kind == .boost {
if let inner_ev = event.inner_event {
VStack(alignment: .leading) {
let prof_model = ProfileModel(pubkey: event.pubkey, damus: damus)
let follow_model = FollowersModel(damus_state: damus, target: event.pubkey)
let prof = damus.profiles.lookup(id: event.pubkey)
let booster_profile = ProfileView(damus_state: damus, profile: prof_model, followers: follow_model)
let booster_profile = ProfileView(damus_state: damus, pubkey: event.pubkey)
NavigationLink(destination: booster_profile) {
Reposted(damus: damus, pubkey: event.pubkey, profile: prof)
@ -86,9 +84,6 @@ struct EventView: View {
TextEvent(damus: damus, event: event, pubkey: pubkey, has_action_bar: has_action_bar, booster_pubkey: nil)
.padding([.top], 6)
}
Divider()
.padding([.top], 4)
}
}
}

View File

@ -71,8 +71,10 @@ struct BuilderEventView: View {
}
}
.frame(minWidth: 0, maxWidth: .infinity)
.cornerRadius(8)
.border(Color.gray.opacity(0.2), width: 1)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.gray.opacity(0.2), lineWidth: 1.0)
)
.onAppear {
self.load()
}

View File

@ -11,6 +11,14 @@ struct EventBody: View {
let damus_state: DamusState
let event: NostrEvent
let size: EventViewKind
let should_show_img: Bool
init(damus_state: DamusState, event: NostrEvent, size: EventViewKind, should_show_img: Bool? = nil) {
self.damus_state = damus_state
self.event = event
self.size = size
self.should_show_img = should_show_img ?? should_show_images(contacts: damus_state.contacts, ev: event, our_pubkey: damus_state.pubkey)
}
var content: String {
event.get_content(damus_state.keypair.privkey)
@ -21,8 +29,6 @@ struct EventBody: View {
ReplyDescription(event: event, profiles: damus_state.profiles)
}
let should_show_img = should_show_images(contacts: damus_state.contacts, ev: event, our_pubkey: damus_state.pubkey, booster_pubkey: nil)
NoteContentView(damus_state: damus_state, event: event, show_images: should_show_img, size: size, artifacts: .just_content(content))
.frame(maxWidth: .infinity, alignment: .leading)
}

View File

@ -47,9 +47,9 @@ struct EventMenuContext: View {
notify(.update_bookmarks, event)
} label: {
let imageName = isBookmarked ? "bookmark.fill" : "bookmark"
let unBookmarkString = NSLocalizedString("Un-Bookmark", comment: "Context menu option for un-bookmarking a note")
let bookmarkString = NSLocalizedString("Bookmark", comment: "Context menu optoin for bookmarking a note")
Label(isBookmarked ? unBookmarkString : bookmarkString, systemImage: imageName)
let removeBookmarkString = NSLocalizedString("Remove Bookmark", comment: "Context menu option for removing a note bookmark.")
let addBookmarkString = NSLocalizedString("Add Bookmark", comment: "Context menu option for adding a note bookmark.")
Label(isBookmarked ? removeBookmarkString : addBookmarkString, systemImage: imageName)
}
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {

View File

@ -31,10 +31,7 @@ struct EventProfile: View {
var body: some View {
HStack(alignment: .center) {
VStack {
let pmodel = ProfileModel(pubkey: pubkey, damus: damus_state)
let pv = ProfileView(damus_state: damus_state, profile: pmodel, followers: FollowersModel(damus_state: damus_state, target: pubkey))
NavigationLink(destination: pv) {
NavigationLink(destination: ProfileView(damus_state: damus_state, pubkey: pubkey)) {
ProfilePicView(pubkey: pubkey, size: pfp_size, highlight: .none, profiles: damus_state.profiles)
}
}

View File

@ -26,13 +26,15 @@ struct ReplyDescription_Previews: PreviewProvider {
}
}
func reply_desc(profiles: Profiles, event: NostrEvent) -> String {
func reply_desc(profiles: Profiles, event: NostrEvent, locale: Locale = Locale.current) -> String {
let desc = make_reply_description(event.tags)
let pubkeys = desc.pubkeys
let n = desc.others
let bundle = bundleForLocale(locale: locale)
if desc.pubkeys.count == 0 {
return NSLocalizedString("Replying to self", comment: "Label to indicate that the user is replying to themself.")
return NSLocalizedString("Replying to self", bundle: bundle, comment: "Label to indicate that the user is replying to themself.")
}
let names: [String] = pubkeys.map {
@ -40,20 +42,16 @@ func reply_desc(profiles: Profiles, event: NostrEvent) -> String {
return Profile.displayName(profile: prof, pubkey: $0)
}
let othersCount = n - pubkeys.count
if names.count > 1 {
let othersCount = n - pubkeys.count
if othersCount == 0 {
return String(format: "Replying to %@ & %@", names[0], names[1])
return String(format: NSLocalizedString("Replying to %@ & %@", bundle: bundle, comment: "Label to indicate that the user is replying to 2 users."), locale: locale, names[0], names[1])
} else {
return String(format: "Replying to %@, %@ & %d others", names[0], names[1], othersCount)
return String(format: bundle.localizedString(forKey: "replying_to_two_and_others", value: nil, table: nil), locale: locale, othersCount, names[0], names[1])
}
}
if othersCount == 0 {
return String(format: "Replying to %@", names[0])
} else {
return String(format: "Replying to %@ & %d others", names[0], othersCount)
}
return String(format: NSLocalizedString("Replying to %@", bundle: bundle, comment: "Label to indicate that the user is replying to 1 user."), locale: locale, names[0])
}

View File

@ -18,20 +18,17 @@ struct TextEvent: View {
HStack(alignment: .top) {
let profile = damus.profiles.lookup(id: pubkey)
let is_anon = event_is_anonymous(ev: event)
VStack {
let pmodel = ProfileModel(pubkey: pubkey, damus: damus)
let pv = ProfileView(damus_state: damus, profile: pmodel, followers: FollowersModel(damus_state: damus, target: pubkey))
NavigationLink(destination: pv) {
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: .none, profiles: damus.profiles)
}
MaybeAnonPfpView(state: damus, is_anon: is_anon, pubkey: pubkey)
Spacer()
}
VStack(alignment: .leading) {
HStack(alignment: .center) {
EventProfileName(pubkey: pubkey, profile: profile, damus: damus, show_friend_confirmed: true, size: .normal)
let pk = is_anon ? "anon" : pubkey
EventProfileName(pubkey: pk, profile: profile, damus: damus, show_friend_confirmed: true, size: .normal)
Text(verbatim: "\(format_relative_time(event.created_at))")
.foregroundColor(.gray)
@ -68,3 +65,18 @@ struct TextEvent_Previews: PreviewProvider {
TextEvent(damus: test_damus_state(), event: test_event, pubkey: "pk", has_action_bar: true, booster_pubkey: nil)
}
}
func event_has_tag(ev: NostrEvent, tag: String) -> Bool {
for t in ev.tags {
if t.count >= 1 && t[0] == tag {
return true
}
}
return false
}
func event_is_anonymous(ev: NostrEvent) -> Bool {
return ev.known_kind == .zap_request && event_has_tag(ev: ev, tag: "anon")
}

View File

@ -19,7 +19,7 @@ struct FollowButtonView: View {
Button {
follow_state = perform_follow_btn_action(follow_state, target: target)
} label: {
Text(follow_btn_txt(follow_state, follows_you: follows_you))
Text(verbatim: "\(follow_btn_txt(follow_state, follows_you: follows_you))")
.frame(width: 105, height: 30)
//.padding(.vertical, 10)
.font(.caption.weight(.bold))

View File

@ -29,7 +29,6 @@ struct FollowersView: View {
@EnvironmentObject var followers: FollowersModel
var body: some View {
let profile = damus_state.profiles.lookup(id: whos)
ScrollView {
LazyVStack(alignment: .leading) {
ForEach(followers.contacts ?? [], id: \.self) { pk in
@ -38,7 +37,7 @@ struct FollowersView: View {
}
.padding()
}
.navigationBarTitle(NSLocalizedString("\(Profile.displayName(profile: profile, pubkey: whos))'s Followers", comment: "Navigation bar title for view that shows who is following a user."))
.navigationBarTitle(NSLocalizedString("Followers", comment: "Navigation bar title for view that shows who is following a user."))
.onAppear {
followers.subscribe()
}
@ -56,8 +55,6 @@ struct FollowingView: View {
let whos: String
var body: some View {
let profile = damus_state.profiles.lookup(id: whos)
let who = Profile.displayName(profile: profile, pubkey: whos)
ScrollView {
LazyVStack(alignment: .leading) {
ForEach(following.contacts, id: \.self) { pk in
@ -72,7 +69,7 @@ struct FollowingView: View {
.onDisappear {
following.unsubscribe()
}
.navigationBarTitle(NSLocalizedString("\(who) following", comment: "Navigation bar title for view that shows who a user is following."))
.navigationBarTitle(NSLocalizedString("Following", comment: "Navigation bar title for view that shows who a user is following."))
}
}

View File

@ -0,0 +1,214 @@
//
// RepostGroupView.swift
// damus
//
// Created by William Casarin on 2023-02-21.
//
import SwiftUI
enum EventGroupType {
case repost(EventGroup)
case reaction(EventGroup)
case zap(ZapGroup)
case profile_zap(ZapGroup)
var events: [NostrEvent] {
switch self {
case .repost(let grp):
return grp.events
case .reaction(let grp):
return grp.events
case .zap(let zapgrp):
return zapgrp.zap_requests()
case .profile_zap(let zapgrp):
return zapgrp.zap_requests()
}
}
}
enum ReactingTo {
case your_post
case tagged_in
case your_profile
}
func determine_reacting_to(our_pubkey: String, ev: NostrEvent?) -> ReactingTo {
guard let ev else {
return .your_profile
}
if ev.pubkey == our_pubkey {
return .your_post
}
return .tagged_in
}
func event_author_name(profiles: Profiles, _ ev: NostrEvent) -> String {
let alice_pk = ev.pubkey
let alice_prof = profiles.lookup(id: alice_pk)
return Profile.displayName(profile: alice_prof, pubkey: alice_pk)
}
/**
Returns a notification string describing user actions in response to an event group type.
The localization keys read by this function are the following (although some keys may not actually be used in practice):
"??" - returned when there are no events associated with the specified event group type.
"reacted_tagged_in_1" - returned when 1 reaction occurred to a post that the current user was tagged in
"reacted_tagged_in_2" - returned when 2 reactions occurred to a post that the current user was tagged in
"reacted_tagged_in_3" - returned when 3 or more reactions occurred to a post that the current user was tagged in
"reacted_your_post_1" - returned when 1 reaction occurred to the current user's post
"reacted_your_post_2" - returned when 2 reactions occurred to the current user's post
"reacted_your_post_3" - returned when 3 or more reactions occurred to the current user's post
"reacted_your_profile_1" - returned when 1 reaction occurred to the current user's profile
"reacted_your_profile_2" - returned when 2 reactions occurred to the current user's profile
"reacted_your_profile_3" - returned when 3 or more reactions occurred to the current user's profile
"reposted_tagged_in_1" - returned when 1 repost occurred to a post that the current user was tagged in
"reposted_tagged_in_2" - returned when 2 reposts occurred to a post that the current user was tagged in
"reposted_tagged_in_3" - returned when 3 or more reposts occurred to a post that the current user was tagged in
"reposted_your_post_1" - returned when 1 repost occurred to the current user's post
"reposted_your_post_2" - returned when 2 reposts occurred to the current user's post
"reposted_your_post_3" - returned when 3 or more reposts occurred to the current user's post
"reposted_your_profile_1" - returned when 1 repost occurred to the current user's profile
"reposted_your_profile_2" - returned when 2 reposts occurred to the current user's profile
"reposted_your_profile_3" - returned when 3 or more reposts occurred to the current user's profile
"zapped_tagged_in_1" - returned when 1 zap occurred to a post that the current user was tagged in
"zapped_tagged_in_2" - returned when 2 zaps occurred to a post that the current user was tagged in
"zapped_tagged_in_3" - returned when 3 or more zaps occurred to a post that the current user was tagged in
"zapped_your_post_1" - returned when 1 zap occurred to the current user's post
"zapped_your_post_2" - returned when 2 zaps occurred to the current user's post
"zapped_your_post_3" - returned when 3 or more zaps occurred to the current user's post
"zapped_your_profile_1" - returned when 1 zap occurred to the current user's profile
"zapped_your_profile_2" - returned when 2 zaps occurred to the current user's profile
"zapped_your_profile_3" - returned when 3 or more zaps occurred to the current user's profile
*/
func reacting_to_text(profiles: Profiles, our_pubkey: String, group: EventGroupType, ev: NostrEvent?, locale: Locale? = nil) -> String {
let verb = reacting_to_verb(group: group)
let reacting_to = determine_reacting_to(our_pubkey: our_pubkey, ev: ev)
let localization_key = "\(verb)_\(reacting_to)_\(min(group.events.count, 3))"
let bundle = bundleForLocale(locale: locale)
switch group.events.count {
case 0:
return NSLocalizedString("??", comment: "")
case 1:
let ev = group.events.first!
let profile = profiles.lookup(id: ev.pubkey)
let display_name = Profile.displayName(profile: profile, pubkey: ev.pubkey)
return String(format: bundle.localizedString(forKey: localization_key, value: bundleForLocale(locale: Locale(identifier: "en-US")).localizedString(forKey: localization_key, value: nil, table: nil), table: nil), locale: locale, display_name)
case 2:
let alice_name = event_author_name(profiles: profiles, group.events[0])
let bob_name = event_author_name(profiles: profiles, group.events[1])
return String(format: bundle.localizedString(forKey: localization_key, value: bundleForLocale(locale: Locale(identifier: "en-US")).localizedString(forKey: localization_key, value: nil, table: nil), table: nil), locale: locale, alice_name, bob_name)
default:
let alice_name = event_author_name(profiles: profiles, group.events.first!)
let count = group.events.count - 1
return String(format: bundle.localizedString(forKey: localization_key, value: bundleForLocale(locale: Locale(identifier: "en-US")).localizedString(forKey: localization_key, value: nil, table: nil), table: nil), locale: locale, count, alice_name)
}
}
func reacting_to_verb(group: EventGroupType) -> String {
switch group {
case .reaction:
return "reacted"
case .repost:
return "reposted"
case .zap: fallthrough
case .profile_zap:
return "zapped"
}
}
struct EventGroupView: View {
let state: DamusState
let event: NostrEvent?
let group: EventGroupType
var GroupDescription: some View {
Text(verbatim: "\(reacting_to_text(profiles: state.profiles, our_pubkey: state.pubkey, group: group, ev: event))")
}
func ZapIcon(_ zapgrp: ZapGroup) -> some View {
let fmt = format_msats_abbrev(zapgrp.msat_total)
return VStack(alignment: .center) {
Image(systemName: "bolt.fill")
.foregroundColor(.orange)
Text("\(fmt)")
.foregroundColor(Color.orange)
}
}
var GroupIcon: some View {
Group {
switch group {
case .repost:
Image(systemName: "arrow.2.squarepath")
.foregroundColor(Color("DamusGreen"))
case .reaction:
Image("shaka-full")
.resizable()
.frame(width: 24, height: 24)
.foregroundColor(.accentColor)
case .profile_zap(let zapgrp):
ZapIcon(zapgrp)
case .zap(let zapgrp):
ZapIcon(zapgrp)
}
}
}
var body: some View {
HStack(alignment: .top) {
GroupIcon
.frame(width: PFP_SIZE + 10)
VStack(alignment: .leading) {
ProfilePicturesView(state: state, events: group.events)
GroupDescription
if let event {
NavigationLink(destination: BuildThreadV2View(damus: state, event_id: event.id)) {
Text(event.content)
.padding([.top], 1)
.foregroundColor(.gray)
}
.buttonStyle(.plain)
}
}
}
.padding([.top], 6)
}
}
let test_encoded_post = "{\"id\": \"8ba545ab96959fe0ce7db31bc10f3ac3aa5353bc4428dbf1e56a7be7062516db\",\"pubkey\": \"7e27509ccf1e297e1df164912a43406218f8bd80129424c3ef798ca3ef5c8444\",\"created_at\": 1677013417,\"kind\": 1,\"tags\": [],\"content\": \"hello\",\"sig\": \"93684f15eddf11f42afbdd81828ee9fc35350344d8650c78909099d776e9ad8d959cd5c4bff7045be3b0b255144add43d0feef97940794a1bc9c309791bebe4a\"}"
let test_repost_1 = NostrEvent(id: "", content: test_encoded_post, pubkey: "pk1", kind: 6, tags: [], createdAt: 1)
let test_repost_2 = NostrEvent(id: "", content: test_encoded_post, pubkey: "pk2", kind: 6, tags: [], createdAt: 1)
let test_reposts = [test_repost_1, test_repost_2]
let test_event_group = EventGroup(events: test_reposts)
struct EventGroupView_Previews: PreviewProvider {
static var previews: some View {
VStack {
EventGroupView(state: test_damus_state(), event: test_event, group: .repost(test_event_group))
.frame(height: 200)
.padding()
EventGroupView(state: test_damus_state(), event: test_event, group: .reaction(test_event_group))
.frame(height: 200)
.padding()
}
}
}

View File

@ -0,0 +1,86 @@
//
// NotificationItemView.swift
// damus
//
// Created by William Casarin on 2023-02-21.
//
import SwiftUI
enum ShowItem {
case show(NostrEvent?)
case dontshow(NostrEvent?)
}
func notification_item_event(events: EventCache, notif: NotificationItem) -> ShowItem {
switch notif {
case .repost(let evid, _):
return .dontshow(events.lookup(evid))
case .reply(let ev):
return .show(ev)
case .reaction(let evid, _):
return .dontshow(events.lookup(evid))
case .event_zap(let evid, _):
return .dontshow(events.lookup(evid))
case .profile_zap:
return .show(nil)
}
}
struct NotificationItemView: View {
let state: DamusState
let item: NotificationItem
var show_item: ShowItem {
notification_item_event(events: state.events, notif: item)
}
func Item(_ ev: NostrEvent?) -> some View {
Group {
switch item {
case .repost(_, let evgrp):
EventGroupView(state: state, event: ev, group: .repost(evgrp))
case .event_zap(_, let zapgrp):
EventGroupView(state: state, event: ev, group: .zap(zapgrp))
case .profile_zap(let grp):
EventGroupView(state: state, event: nil, group: .profile_zap(grp))
case .reaction(_, let evgrp):
EventGroupView(state: state, event: ev, group: .reaction(evgrp))
case .reply(let ev):
NavigationLink(destination: BuildThreadV2View(damus: state, event_id: ev.id)) {
EventView(damus: state, event: ev, has_action_bar: true)
}
.buttonStyle(.plain)
}
Divider()
.padding([.top,.bottom], 5)
}
}
var body: some View {
Group {
switch show_item {
case .show(let ev):
Item(ev)
case .dontshow(let ev):
if let ev {
Item(ev)
}
}
}
}
}
let test_notification_item: NotificationItem = .repost("evid", test_event_group)
struct NotificationItemView_Previews: PreviewProvider {
static var previews: some View {
NotificationItemView(state: test_damus_state(), item: test_notification_item)
}
}

View File

@ -0,0 +1,50 @@
//
// NotificationsView.swift
// damus
//
// Created by William Casarin on 2023-02-21.
//
import SwiftUI
struct NotificationsView: View {
let state: DamusState
@ObservedObject var notifications: NotificationsModel
var body: some View {
ScrollViewReader { scroller in
ScrollView {
LazyVStack(alignment: .leading) {
Color.white.opacity(0)
.id("startblock")
.frame(height: 5)
ForEach(notifications.notifications, id: \.id) { item in
NotificationItemView(state: state, item: item)
}
}
.background(GeometryReader { proxy -> Color in
DispatchQueue.main.async {
handle_scroll_queue(proxy, queue: self.notifications)
}
return Color.clear
})
.padding(.horizontal)
}
.coordinateSpace(name: "scroll")
.onReceive(handle_notify(.scroll_to_top)) { notif in
let _ = notifications.flush()
self.notifications.should_queue = false
scroll_to_event(scroller: scroller, id: "startblock", delay: 0.0, animate: true, anchor: .top)
}
}
.onAppear {
let _ = notifications.flush()
}
}
}
struct NotificationsView_Previews: PreviewProvider {
static var previews: some View {
NotificationsView(state: test_damus_state(), notifications: NotificationsModel())
}
}

View File

@ -0,0 +1,37 @@
//
// ProfilePicturesView.swift
// damus
//
// Created by William Casarin on 2023-02-22.
//
import SwiftUI
struct ProfilePicturesView: View {
let state: DamusState
let events: [NostrEvent]
@State var nav_target: String? = nil
@State var navigating: Bool = false
var body: some View {
NavigationLink(destination: ProfileView(damus_state: state, pubkey: nav_target ?? ""), isActive: $navigating) {
EmptyView()
}
HStack {
ForEach(events.prefix(8)) { ev in
ProfilePicView(pubkey: ev.pubkey, size: 32.0, highlight: .none, profiles: state.profiles)
.onTapGesture {
nav_target = ev.pubkey
navigating = true
}
}
}
}
}
struct ProfilePicturesView_Previews: PreviewProvider {
static var previews: some View {
ProfilePicturesView(state: test_damus_state(), events: [test_event, test_event])
}
}

View File

@ -36,41 +36,43 @@ struct ParticipantsView: View {
Spacer()
}
VStack {
ForEach(originalReferences.pRefs) { participant in
let pubkey = participant.id
HStack {
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: .none, profiles: damus_state.profiles)
VStack(alignment: .leading) {
let profile = damus_state.profiles.lookup(id: pubkey)
ProfileName(pubkey: pubkey, profile: profile, damus: damus_state, show_friend_confirmed: false, show_nip5_domain: false)
if let about = profile?.about {
Text(FollowUserView.markdown.process(about))
.lineLimit(3)
.font(.footnote)
ScrollView {
ForEach(originalReferences.pRefs) { participant in
let pubkey = participant.id
HStack {
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: .none, profiles: damus_state.profiles)
VStack(alignment: .leading) {
let profile = damus_state.profiles.lookup(id: pubkey)
ProfileName(pubkey: pubkey, profile: profile, damus: damus_state, show_friend_confirmed: false, show_nip5_domain: false)
if let about = profile?.about {
Text(FollowUserView.markdown.process(about))
.lineLimit(3)
.font(.footnote)
}
}
Spacer()
Image(systemName: "checkmark.circle.fill")
.font(.system(size: 30))
.foregroundColor(references.contains(participant) ? .purple : .gray)
}
Spacer()
Image(systemName: "checkmark.circle.fill")
.font(.system(size: 30))
.foregroundColor(references.contains(participant) ? .purple : .gray)
}
.onTapGesture {
if references.contains(participant) {
references = references.filter {
$0 != participant
}
} else {
.onTapGesture {
if references.contains(participant) {
// Don't add it twice
references = references.filter {
$0 != participant
}
} else {
references.append(participant)
if references.contains(participant) {
// Don't add it twice
} else {
references.append(participant)
}
}
}
}
}
}
}
}
Spacer()
}

View File

@ -15,7 +15,7 @@ enum NostrPostResult {
let POST_PLACEHOLDER = NSLocalizedString("Type your post here...", comment: "Text box prompt to ask user to type their post.")
struct PostView: View {
@State var post: String = ""
@State var post: NSMutableAttributedString = NSMutableAttributedString()
@FocusState var focus: Bool
@State var showPrivateKeyWarning: Bool = false
@ -44,7 +44,14 @@ struct PostView: View {
if replying_to?.known_kind == .chat {
kind = .chat
}
let content = self.post.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
post.enumerateAttributes(in: NSRange(location: 0, length: post.length), options: []) { attributes, range, stop in
if let link = attributes[.link] as? String {
post.replaceCharacters(in: range, with: link)
}
}
let content = self.post.string.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
let new_post = NostrPost(content: content, references: references, kind: kind)
NotificationCenter.default.post(name: .post, object: NostrPostResult.post(new_post))
@ -52,14 +59,14 @@ struct PostView: View {
if let replying_to {
damus_state.drafts.replies.removeValue(forKey: replying_to)
} else {
damus_state.drafts.post = ""
damus_state.drafts.post = NSMutableAttributedString(string: "")
}
dismiss()
}
var is_post_empty: Bool {
return post.allSatisfy { $0.isWhitespace }
return post.string.allSatisfy { $0.isWhitespace }
}
var body: some View {
@ -74,7 +81,7 @@ struct PostView: View {
if !is_post_empty {
Button(NSLocalizedString("Post", comment: "Button to post a note.")) {
showPrivateKeyWarning = contentContainsPrivateKey(self.post)
showPrivateKeyWarning = contentContainsPrivateKey(self.post.string)
if !showPrivateKeyWarning {
self.send_post()
@ -97,7 +104,7 @@ struct PostView: View {
VStack(alignment: .leading) {
ZStack(alignment: .topLeading) {
TextEditor(text: $post)
TextViewWrapper(attributedText: $post)
.focused($focus)
.textInputAutocapitalization(.sentences)
.onChange(of: post) { _ in
@ -108,7 +115,7 @@ struct PostView: View {
}
}
if post.isEmpty {
if post.string.isEmpty {
Text(POST_PLACEHOLDER)
.padding(.top, 8)
.padding(.leading, 4)
@ -120,7 +127,7 @@ struct PostView: View {
}
// This if-block observes @ for tagging
if let searching = get_searching_string(post) {
if let searching = get_searching_string(post.string) {
VStack {
Spacer()
UserSearch(damus_state: damus_state, search: searching, post: $post)
@ -130,7 +137,7 @@ struct PostView: View {
.onAppear() {
if let replying_to {
if damus_state.drafts.replies[replying_to] == nil {
damus_state.drafts.replies[replying_to] = ""
damus_state.drafts.post = NSMutableAttributedString(string: "")
}
if let p = damus_state.drafts.replies[replying_to] {
post = p
@ -144,10 +151,10 @@ struct PostView: View {
}
}
.onDisappear {
if let replying_to, let reply = damus_state.drafts.replies[replying_to], reply.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
if let replying_to, let reply = damus_state.drafts.replies[replying_to], reply.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
damus_state.drafts.replies.removeValue(forKey: replying_to)
} else if replying_to == nil && damus_state.drafts.post.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
damus_state.drafts.post = ""
} else if replying_to == nil && damus_state.drafts.post.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
damus_state.drafts.post = NSMutableAttributedString(string : "")
}
}
.padding()

View File

@ -20,7 +20,8 @@ struct SearchedUser: Identifiable {
struct UserSearch: View {
let damus_state: DamusState
let search: String
@Binding var post: String
@Binding var post: NSMutableAttributedString
var users: [SearchedUser] {
guard let contacts = damus_state.contacts.event else {
@ -39,7 +40,26 @@ struct UserSearch: View {
guard let pk = bech32_pubkey(user.pubkey) else {
return
}
post = post.replacingOccurrences(of: "@"+search, with: "@"+pk+" ")
while post.string.last != "@" {
post.deleteCharacters(in: NSRange(location: post.length - 1, length: 1))
}
post.deleteCharacters(in: NSRange(location: post.length - 1, length: 1))
var tagString = ""
if let name = user.profile?.name {
tagString = "@\(name)\u{200B} "
}
let tagAttributedString = NSMutableAttributedString(string: tagString,
attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 18.0),
NSAttributedString.Key.link: "@\(pk)"])
tagAttributedString.removeAttribute(.link, range: NSRange(location: tagAttributedString.length - 2, length: 2))
tagAttributedString.addAttributes([NSAttributedString.Key.foregroundColor: UIColor.label], range: NSRange(location: tagAttributedString.length - 2, length: 2))
let mutableString = NSMutableAttributedString()
mutableString.append(post)
mutableString.append(tagAttributedString)
post = mutableString
}
}
}
@ -49,7 +69,7 @@ struct UserSearch: View {
struct UserSearch_Previews: PreviewProvider {
static let search: String = "jb55"
@State static var post: String = "some @jb55"
@State static var post: NSMutableAttributedString = NSMutableAttributedString(string: "some @jb55")
static var previews: some View {
UserSearch(damus_state: test_damus_state(), search: search, post: $post)

View File

@ -0,0 +1,46 @@
//
// MaybeAnonPfpView.swift
// damus
//
// Created by William Casarin on 2023-02-26.
//
import SwiftUI
struct MaybeAnonPfpView: View {
let state: DamusState
let is_anon: Bool
let pubkey: String
init(state: DamusState, event: NostrEvent, pubkey: String) {
self.state = state
self.is_anon = event_is_anonymous(ev: event)
self.pubkey = pubkey
}
init(state: DamusState, is_anon: Bool, pubkey: String) {
self.state = state
self.is_anon = is_anon
self.pubkey = pubkey
}
var body: some View {
Group {
if is_anon {
Image(systemName: "person.fill.questionmark")
.font(.largeTitle)
.frame(width: PFP_SIZE, height: PFP_SIZE)
} else {
NavigationLink(destination: ProfileView(damus_state: state, pubkey: pubkey)) {
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: .none, profiles: state.profiles)
}
}
}
}
}
struct MaybeAnonPfpView_Previews: PreviewProvider {
static var previews: some View {
MaybeAnonPfpView(state: test_damus_state(), is_anon: true, pubkey: "anon")
}
}

View File

@ -67,7 +67,7 @@ struct ProfileName: View {
var body: some View {
HStack(spacing: 2) {
Text(prefix + String(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey)))
Text(verbatim: "\(prefix)\(String(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey)))")
.font(.body)
.fontWeight(prefix == "@" ? .none : .bold)
if let nip05 = current_nip05 {
@ -136,11 +136,11 @@ struct EventProfileName: View {
.font(.body.weight(.bold))
.padding([.trailing], 2)
Text("@" + String(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey)))
Text(verbatim: "@\(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey))")
.foregroundColor(Color("DamusMediumGrey"))
.font(eventviewsize_to_font(size))
} else {
Text(String(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey)))
Text(verbatim: "\(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey))")
.font(eventviewsize_to_font(size))
.fontWeight(.bold)
}

View File

@ -49,6 +49,16 @@ func follow_btn_enabled_state(_ fs: FollowState) -> Bool {
}
}
func followersCountString(_ count: Int, locale: Locale = Locale.current) -> String {
let bundle = bundleForLocale(locale: locale)
return String(format: bundle.localizedString(forKey: "followers_count", value: nil, table: nil), locale: locale, count)
}
func relaysCountString(_ count: Int, locale: Locale = Locale.current) -> String {
let bundle = bundleForLocale(locale: locale)
return String(format: bundle.localizedString(forKey: "relays_count", value: nil, table: nil), locale: locale, count)
}
struct EditButton: View {
let damus_state: DamusState
@ -100,8 +110,6 @@ struct ProfileView: View {
static let markdown = Markdown()
@State private var selected_tab: ProfileTab = .posts
@StateObject var profile: ProfileModel
@StateObject var followers: FollowersModel
@State private var showingEditProfile = false
@State var showing_select_wallet: Bool = false
@State var is_zoomed: Bool = false
@ -110,6 +118,21 @@ struct ProfileView: View {
@State var filter_state : FilterState = .posts
@State var yOffset: CGFloat = 0
@StateObject var profile: ProfileModel
@StateObject var followers: FollowersModel
init(damus_state: DamusState, profile: ProfileModel, followers: FollowersModel) {
self.damus_state = damus_state
self._profile = StateObject(wrappedValue: profile)
self._followers = StateObject(wrappedValue: followers)
}
init(damus_state: DamusState, pubkey: String) {
self.damus_state = damus_state
self._profile = StateObject(wrappedValue: ProfileModel(pubkey: pubkey, damus: damus_state))
self._followers = StateObject(wrappedValue: FollowersModel(damus_state: damus_state, target: pubkey))
}
@Environment(\.dismiss) var dismiss
@Environment(\.colorScheme) var colorScheme
@Environment(\.openURL) var openURL
@ -319,7 +342,8 @@ struct ProfileView: View {
.foregroundColor(.gray)
} else {
let followerCount = followers.count!
Text("\(Text(verbatim: "\(followerCount)").font(.subheadline.weight(.medium))) \(Text(String(format: Bundle.main.localizedString(forKey: "followers_count", value: nil, table: nil), followerCount)).font(.subheadline).foregroundColor(.gray))", comment: "Sentence composed of 2 variables to describe how many people are following a user. In source English, the first variable is the number of followers, and the second variable is 'Follower' or 'Followers'.")
let noun_text = Text(verbatim: "\(followersCountString(followerCount))").font(.subheadline).foregroundColor(.gray)
Text("\(Text("\(followerCount)").font(.subheadline.weight(.medium))) \(noun_text)", comment: "Sentence composed of 2 variables to describe how many people are following a user. In source English, the first variable is the number of followers, and the second variable is 'Follower' or 'Followers'.")
}
}
}
@ -343,7 +367,8 @@ struct ProfileView: View {
let following_model = FollowingModel(damus_state: damus_state, contacts: contacts)
NavigationLink(destination: FollowingView(damus_state: damus_state, following: following_model, whos: profile.pubkey)) {
HStack {
Text("\(Text(verbatim: "\(profile.following)").font(.subheadline.weight(.medium))) \(Text("Following", comment: "Part of a larger sentence to describe how many profiles a user is following.").font(.subheadline).foregroundColor(.gray))", comment: "Sentence composed of 2 variables to describe how many profiles a user is following. In source English, the first variable is the number of profiles being followed, and the second variable is 'Following'.")
let noun_text = Text("Following", comment: "Text on the user profile page next to the number of accounts a user is following.").font(.subheadline).foregroundColor(.gray)
Text("\(Text("\(profile.following)").font(.subheadline.weight(.medium))) \(noun_text)", comment: "Sentence composed of 2 variables to describe how many profiles a user is following. In source English, the first variable is the number of profiles being followed, and the second variable is 'Following'.")
}
}
.buttonStyle(PlainButtonStyle())
@ -366,7 +391,8 @@ struct ProfileView: View {
if let relays = profile.relays {
// Only open relay config view if the user is logged in with private key and they are looking at their own profile.
let relay_text = Text("\(Text(verbatim: "\(relays.keys.count)").font(.subheadline.weight(.medium))) \(Text(String(format: Bundle.main.localizedString(forKey: "relays_count", value: nil, table: nil), relays.keys.count)).font(.subheadline).foregroundColor(.gray))", comment: "Sentence composed of 2 variables to describe how many relay servers a user is connected. In source English, the first variable is the number of relay servers, and the second variable is 'Relay' or 'Relays'.")
let noun_text = Text(verbatim: "\(relaysCountString(relays.keys.count))").font(.subheadline).foregroundColor(.gray)
let relay_text = Text("\(Text("\(relays.keys.count)").font(.subheadline.weight(.medium))) \(noun_text)", comment: "Sentence composed of 2 variables to describe how many relay servers a user is connected. In source English, the first variable is the number of relay servers, and the second variable is 'Relay' or 'Relays'.")
if profile.pubkey == damus_state.pubkey && damus_state.is_privkey_user {
NavigationLink(destination: RelayConfigView(state: damus_state)) {
relay_text
@ -443,9 +469,7 @@ struct ProfileView: View {
struct ProfileView_Previews: PreviewProvider {
static var previews: some View {
let ds = test_damus_state()
let followers = FollowersModel(damus_state: ds, target: ds.pubkey)
let profile_model = ProfileModel(pubkey: ds.pubkey, damus: ds)
ProfileView(damus_state: ds, profile: profile_model, followers: followers)
ProfileView(damus_state: ds, pubkey: ds.pubkey)
}
}
@ -506,7 +530,7 @@ struct KeyView: View {
.symbolRenderingMode(.palette)
}
.padding(.leading,4)
Text(abbrev_pubkey(bech32, amount: 16))
Text(verbatim: "\(abbrev_pubkey(bech32, amount: 16))")
.font(.footnote)
.foregroundColor(keyColor())
}

View File

@ -26,7 +26,7 @@ struct RelayFilterView: View {
}
var body: some View {
Text("To filter your \(timeline.rawValue) feed, please choose applicable relays from the list below:", comment: "Instructions on how to filter a specific timeline feed by choosing relay servers to filter on.")
Text("Please choose relays from the list below to filter the current feed:", comment: "Instructions on how to filter a specific timeline feed by choosing relay servers to filter on.")
.padding()
.padding(.top, 20)
.padding(.bottom, 0)

View File

@ -38,7 +38,7 @@ struct SaveKeysView: View {
.foregroundColor(.white)
.padding(.bottom, 10)
Text("This is your account ID, you can give this to your friends so that they can follow you. Click to copy.", comment: "Label to describe that a public key is the user's account ID and what they can do with it.")
Text("This is your account ID, you can give this to your friends so that they can follow you. Tap to copy.", comment: "Label to describe that a public key is the user's account ID and what they can do with it.")
.foregroundColor(.white)
.padding(.bottom, 10)

View File

@ -108,6 +108,7 @@ struct SideMenuView: View {
navLabel(title: NSLocalizedString("Settings", comment: "Sidebar menu label for accessing the app settings"), systemImage: "gear")
}
}
.labelStyle(SideMenuLabelStyle())
.padding([.top, .bottom], verticalSpacing)
}
}
@ -175,6 +176,17 @@ struct SideMenuView: View {
.foregroundColor(textColor())
.frame(maxWidth: .infinity, alignment: .leading)
}
struct SideMenuLabelStyle: LabelStyle {
func makeBody(configuration: Configuration) -> some View {
HStack(alignment: .center, spacing: 8) {
configuration.icon
.frame(width: 24, height: 24)
.aspectRatio(contentMode: .fit)
configuration.title
}
}
}
}
struct Previews_SideMenuView_Previews: PreviewProvider {

View File

@ -0,0 +1,44 @@
//
// TextViewWrapper.swift
// damus
//
// Created by Swift on 2/24/23.
//
import SwiftUI
struct TextViewWrapper: UIViewRepresentable {
@Binding var attributedText: NSMutableAttributedString
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.delegate = context.coordinator
textView.font = UIFont.systemFont(ofSize: 18)
textView.textColor = UIColor.label
let linkAttributes: [NSAttributedString.Key : Any] = [
NSAttributedString.Key.foregroundColor: UIColor(Color.accentColor)]
textView.linkTextAttributes = linkAttributes
return textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.attributedText = attributedText
}
func makeCoordinator() -> Coordinator {
Coordinator(attributedText: $attributedText)
}
class Coordinator: NSObject, UITextViewDelegate {
@Binding var attributedText: NSMutableAttributedString
init(attributedText: Binding<NSMutableAttributedString>) {
_attributedText = attributedText
}
func textViewDidChange(_ textView: UITextView) {
attributedText = NSMutableAttributedString(attributedString: textView.attributedText)
}
}
}

View File

@ -265,6 +265,10 @@ struct ThreadV2View: View {
navigating: $navigating,
selected: false
)
Divider()
.padding(.top, 4)
.padding(.leading, 25 * 2)
}
}.background(GeometryReader { geometry in
// get the height and width of the EventView view
@ -289,15 +293,20 @@ struct ThreadV2View: View {
).id("main")
// MARK: - Responses of the actual event view
ForEach(thread.childEvents, id: \.id) { event in
MutedEventView(
damus_state: damus,
event: event,
scroller: reader,
nav_target: $nav_target,
navigating: $navigating,
selected: false
)
LazyVStack {
ForEach(thread.childEvents, id: \.id) { event in
MutedEventView(
damus_state: damus,
event: event,
scroller: nil,
nav_target: $nav_target,
navigating: $navigating,
selected: false
)
Divider()
.padding([.top], 4)
}
}
}.padding()
}.navigationBarTitle(NSLocalizedString("Thread", comment: "Navigation bar title for note thread."))

View File

@ -42,6 +42,9 @@ struct InnerTimelineView: View {
navigating = true
}
.padding(.top, 10)
Divider()
.padding([.top], 10)
}
}
}

View File

@ -23,7 +23,7 @@ struct LoadMoreButton: View {
Group {
if events.queued > 0 {
Button(action: click) {
Text("Load \(events.queued) more")
Text("Load \(events.queued) more", comment: "Button text for loading more events, where the variable is the number of events.")
}
.font(.system(size: 14, weight: .bold))
.padding(10)

View File

@ -27,17 +27,6 @@ struct TimelineView: View {
MainContent
}
func handle_scroll(_ proxy: GeometryProxy) {
let offset = -proxy.frame(in: .named("scroll")).origin.y
guard offset >= 0 else {
return
}
let val = offset > 0
if self.events.should_queue != val {
self.events.should_queue = val
}
}
var realtime_bar_opacity: Double {
colorScheme == .dark ? 0.2 : 0.1
}
@ -55,7 +44,7 @@ struct TimelineView: View {
.disabled(loading)
.background(GeometryReader { proxy -> Color in
DispatchQueue.main.async {
handle_scroll(proxy)
handle_scroll_queue(proxy, queue: self.events)
}
return Color.clear
})
@ -82,3 +71,18 @@ struct TimelineView_Previews: PreviewProvider {
}
protocol ScrollQueue {
var should_queue: Bool { get }
func set_should_queue(_ val: Bool)
}
func handle_scroll_queue(_ proxy: GeometryProxy, queue: ScrollQueue) {
let offset = -proxy.frame(in: .named("scroll")).origin.y
guard offset >= 0 else {
return
}
let val = offset > 0
if queue.should_queue != val {
queue.set_should_queue(val)
}
}

View File

@ -0,0 +1,215 @@
//
// CustomizeZapView.swift
// damus
//
// Created by William Casarin on 2023-02-25.
//
import SwiftUI
import Combine
enum ZapType {
case pub
case anon
case non_zap
}
struct ZapAmountItem: Identifiable, Hashable {
let amount: Int
let icon: String
var id: String {
return icon
}
}
func get_default_zap_amount_item(_ pubkey: String) -> ZapAmountItem {
let def = get_default_zap_amount(pubkey: pubkey) ?? 1000
return ZapAmountItem(amount: def, icon: "🤙")
}
func get_zap_amount_items(pubkey: String) -> [ZapAmountItem] {
let def_item = get_default_zap_amount_item(pubkey)
var entries = [
ZapAmountItem(amount: 500, icon: "🙂"),
ZapAmountItem(amount: 5000, icon: "💜"),
ZapAmountItem(amount: 10_000, icon: "😍"),
ZapAmountItem(amount: 20_000, icon: "🤩"),
ZapAmountItem(amount: 50_000, icon: "🔥"),
ZapAmountItem(amount: 100_000, icon: "🚀"),
ZapAmountItem(amount: 1_000_000, icon: "🤯"),
]
entries.append(def_item)
entries.sort { $0.amount < $1.amount }
return entries
}
struct CustomizeZapView: View {
let state: DamusState
let event: NostrEvent
let lnurl: String
@State var comment: String
@State var custom_amount: String
@State var custom_amount_sats: Int?
@State var selected_amount: ZapAmountItem
@State var zap_type: ZapType
@State var invoice: String
@State var error: String?
@State var showing_wallet_selector: Bool
@State var zapping: Bool
let zap_amounts: [ZapAmountItem]
@Environment(\.dismiss) var dismiss
init(state: DamusState, event: NostrEvent, lnurl: String) {
self._comment = State(initialValue: "")
self.event = event
self.zap_amounts = get_zap_amount_items(pubkey: state.pubkey)
self._error = State(initialValue: nil)
self._invoice = State(initialValue: "")
self._showing_wallet_selector = State(initialValue: false)
self._custom_amount = State(initialValue: "")
self._zap_type = State(initialValue: .pub)
let selected = get_default_zap_amount_item(state.pubkey)
self._selected_amount = State(initialValue: selected)
self._custom_amount_sats = State(initialValue: nil)
self._zapping = State(initialValue: false)
self.lnurl = lnurl
self.state = state
}
var ZapTypePicker: some View {
Picker(NSLocalizedString("Zap Type", comment: "Header text to indicate that the picker below it is to choose the type of zap to send."), selection: $zap_type) {
Text("Public", comment: "Picker option to indicate that a zap should be sent publicly and identify the user as who sent it.").tag(ZapType.pub)
Text("Anonymous", comment: "Picker option to indicate that a zap should be sent anonymously and not identify the user as who sent it.").tag(ZapType.anon)
Text("Non-Zap", comment: "Picker option to indicate that sats should be sent to the user's wallet as a regular Lightning payment, not as a zap.").tag(ZapType.non_zap)
}
.pickerStyle(.segmented)
}
var AmountPicker: some View {
Picker(NSLocalizedString("Zap Amount", comment: "Title of picker that allows selection of predefined amounts to zap."), selection: $selected_amount) {
ForEach(zap_amounts) { entry in
let fmt = format_msats_abbrev(Int64(entry.amount) * 1000)
HStack(alignment: .firstTextBaseline) {
Text("\(entry.icon)")
.frame(width: 30)
Text("\(fmt)")
.frame(width: 50)
}
.tag(entry)
}
}
.pickerStyle(.wheel)
}
func receive_zap(notif: Notification) {
let zap_ev = notif.object as! ZappingEvent
guard zap_ev.is_custom else {
return
}
guard zap_ev.event.id == event.id else {
return
}
self.zapping = false
switch zap_ev.type {
case .failed(let err):
switch err {
case .fetching_invoice:
self.error = "Error fetching lightning invoice"
case .bad_lnurl:
self.error = "Invalid lightning address"
}
break
case .got_zap_invoice(let inv):
if should_show_wallet_selector(state.pubkey) {
self.invoice = inv
self.showing_wallet_selector = true
} else {
open_with_wallet(wallet: get_default_wallet(state.pubkey).model, invoice: inv)
self.showing_wallet_selector = false
dismiss()
}
}
}
var body: some View {
MainContent
.sheet(isPresented: $showing_wallet_selector) {
SelectWalletView(showingSelectWallet: $showing_wallet_selector, our_pubkey: state.pubkey, invoice: invoice)
}
.onReceive(handle_notify(.zapping)) { notif in
receive_zap(notif: notif)
}
.ignoresSafeArea()
}
var MainContent: some View {
VStack(alignment: .leading) {
Form {
Section(content: {
AmountPicker
}, header: {
Text("Zap Amount in sats", comment: "Header text to indicate that the picker below it is to choose a pre-defined amount of sats to zap.")
})
Section(content: {
TextField(String("100000"), text: $custom_amount)
.keyboardType(.numberPad)
.onReceive(Just(custom_amount)) { newValue in
if let parsed = handle_string_amount(new_value: newValue) {
self.custom_amount = String(parsed)
self.custom_amount_sats = parsed
}
}
}, header: {
Text("Custom Zap Amount", comment: "Header text to indicate that the text field below it is to enter a custom zap amount.")
})
.dismissKeyboardOnTap()
Section(content: {
TextField(NSLocalizedString("Awesome post!", comment: "Placeholder text for a comment to send as part of a zap to the user."), text: $comment)
}, header: {
Text("Comment", comment: "Header text to indicate that the text field below it is a comment that will be used to send as part of a zap to the user.")
})
.dismissKeyboardOnTap()
Section(content: {
ZapTypePicker
}, header: {
Text("Zap Type", comment: "Header text to indicate that the picker below it is to choose the type of zap to send.")
})
if zapping {
Text("Zapping...", comment: "Text to indicate that the app is in the process of sending a zap.")
} else {
Button(NSLocalizedString("Zap", comment: "Button to send a zap.")) {
let amount = custom_amount_sats ?? selected_amount.amount
send_zap(damus_state: state, event: event, lnurl: lnurl, is_custom: true, comment: comment, amount_sats: amount, zap_type: zap_type)
self.zapping = true
}
.zIndex(16)
}
if let error {
Text(error)
.foregroundColor(.red)
}
}
}
}
}
struct CustomizeZapView_Previews: PreviewProvider {
static var previews: some View {
CustomizeZapView(state: test_damus_state(), event: test_event, lnurl: "")
.frame(width: 400, height: 600)
}
}

Binary file not shown.

Binary file not shown.

View File

@ -4,51 +4,51 @@
<dict>
<key>collapsed_event_view_other_notes</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@NOTES@</string>
<key>NOTES</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>few</key>
<string>%d منشورات اضافية</string>
<key>many</key>
<string>%d منشورات اضافية</string>
<key>one</key>
<string>%d منشور اضافي</string>
<key>other</key>
<string>%d منشورات اضافية</string>
<key>two</key>
<string>%d منشوران</string>
<key>zero</key>
<string>%d منشورات أخرى</string>
<string>... %d منشورات أخرى ...</string>
<key>one</key>
<string>... %d منشور اضافي ...</string>
<key>two</key>
<string>... %d منشوران ...</string>
<key>few</key>
<string>... %d منشورات اضافية ...</string>
<key>many</key>
<string>... %d منشورات اضافية ...</string>
<key>other</key>
<string>... %d منشورات اضافية ...</string>
</dict>
<key>NSStringLocalizedFormatKey</key>
<string>··· %#@NOTES@ ···</string>
</dict>
<key>followers_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@FOLLOWERS@</string>
<key>FOLLOWERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>few</key>
<string>المتابعون</string>
<key>many</key>
<string>المتابعون</string>
<key>one</key>
<string>متابع</string>
<key>other</key>
<string>المتابعون</string>
<key>two</key>
<string>متابعان</string>
<key>zero</key>
<string>متابع</string>
<key>one</key>
<string>متابع</string>
<key>two</key>
<string>متابعان</string>
<key>few</key>
<string>المتابِعون</string>
<key>many</key>
<string>المتابِعون</string>
<key>other</key>
<string>المتابِعون</string>
</dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@FOLLOWERS@</string>
</dict>
<key>reactions_count</key>
<dict>
@ -60,18 +60,18 @@
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>zero</key>
<string>تفاعل</string>
<key>one</key>
<string>تفاعل</string>
<key>two</key>
<string>تفاعلان</string>
<key>few</key>
<string>تفاعلات</string>
<key>many</key>
<string>تفاعل</string>
<key>one</key>
<string>تفاعل</string>
<key>other</key>
<string>تفاعل</string>
<key>two</key>
<string>تفاعل</string>
<key>zero</key>
<string>تفاعل</string>
</dict>
</dict>
<key>relays_count</key>
@ -84,66 +84,66 @@
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>zero</key>
<string>موصّل</string>
<key>one</key>
<string> موصّل</string>
<key>two</key>
<string>موصّلان</string>
<key>few</key>
<string>موصّلات</string>
<key>many</key>
<string>موصّلات</string>
<key>one</key>
<string> موصّل</string>
<string>موصّل</string>
<key>other</key>
<string>موصّلات</string>
<key>two</key>
<string>موصّلان</string>
<key>zero</key>
<string>موصّل</string>
</dict>
</dict>
<key>replying_to_one_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>رد على %@%#@OTHERS@</string>
<string>%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>few</key>
<string> &amp; %d آخرون</string>
<key>many</key>
<string> &amp; %d آخرون</string>
<key>one</key>
<string>&amp; %d آخر</string>
<key>other</key>
<string>&amp; %d آخرين</string>
<key>two</key>
<string> &amp; %d آخران</string>
<key>zero</key>
<string></string>
<string>رد على %2$@</string>
<key>one</key>
<string>الرد على %2$@ &amp; %1$d آخر</string>
<key>two</key>
<string>الرد على %2$@ &amp; %1$d آخرين</string>
<key>few</key>
<string>الرد على %2$@ &amp; %1$d آخرين</string>
<key>many</key>
<string>الرد على %2$@ &amp; %1$d آخرين</string>
<key>other</key>
<string>الرد على %2$@ &amp; %1$d آخرين</string>
</dict>
</dict>
<key>replying_to_two_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>رد على%@, %@%#@OTHERS@</string>
<string>%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>few</key>
<string> &amp; %d آخرون</string>
<key>many</key>
<string> &amp; %d آخرون</string>
<key>one</key>
<string>&amp; %d آخر</string>
<key>other</key>
<string>&amp; %d آخرين</string>
<key>two</key>
<string> &amp; %d آخران</string>
<key>zero</key>
<string></string>
<string>الرد على %2$@, %3$@ &amp; %1$d others</string>
<key>one</key>
<string>الرد على %2$@, %3$@ &amp; %1$d آخر</string>
<key>two</key>
<string>الرد على %2$@, %3$@ &amp; %1$d آخرين</string>
<key>few</key>
<string>الرد على %2$@, %3$@ &amp; %1$d آخرين</string>
<key>many</key>
<string>الرد على %2$@, %3$@ &amp; %1$d آخرين</string>
<key>other</key>
<string>الرد على %2$@, %3$@ &amp; %1$d آخرين</string>
</dict>
</dict>
<key>reposts_count</key>
@ -156,18 +156,18 @@
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>few</key>
<string>اعادات نشر</string>
<key>many</key>
<key>zero</key>
<string>اعادات نشر</string>
<key>one</key>
<string>اعادة نشر</string>
<key>other</key>
<string>اعادات نشر</string>
<key>two</key>
<string>اعادتا نشر</string>
<key>few</key>
<string>اعادات نشر</string>
<key>zero</key>
<string>اعادات نشر</string>
<key>many</key>
<string>اعادة نشر</string>
<key>other</key>
<string>اعادة نشر</string>
</dict>
</dict>
<key>sats_count</key>
@ -180,18 +180,18 @@
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>@</string>
<key>zero</key>
<string>%2$@ ساتوشي</string>
<key>one</key>
<string>%2$@ ساتوشي</string>
<key>two</key>
<string>%2$@ ساتوشي</string>
<key>few</key>
<string>%2$@ ساتوشي</string>
<key>many</key>
<string>%2$@ ساتوشي</string>
<key>one</key>
<string>%2$@ ساتوشي</string>
<key>other</key>
<string>%2$@ ساتوشي</string>
<key>two</key>
<string>%2$@ ساتوشي</string>
<key>zero</key>
<string>%2$@ ساتوشي</string>
</dict>
</dict>
<key>zaps_count</key>
@ -204,18 +204,18 @@
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>few</key>
<string>وميض</string>
<key>many</key>
<key>zero</key>
<string>وميض</string>
<key>one</key>
<string>ومضة</string>
<key>other</key>
<string>وميض</string>
<key>two</key>
<string>وميض</string>
<key>zero</key>
<string>وميض</string>
<string>ومضتان</string>
<key>few</key>
<string>ومضات</string>
<key>many</key>
<string>ومضة</string>
<key>other</key>
<string>ومضة</string>
</dict>
</dict>
</dict>

Binary file not shown.

Binary file not shown.

View File

@ -4,43 +4,43 @@
<dict>
<key>collapsed_event_view_other_notes</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@NOTES@</string>
<key>NOTES</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>few</key>
<string>%d other notes</string>
<key>many</key>
<string>%d other notes</string>
<key>one</key>
<string>%d jiná poznámka</string>
<string>... %d jiná poznámka ...</string>
<key>few</key>
<string>... %d other notes ...</string>
<key>many</key>
<string>... %d other notes ...</string>
<key>other</key>
<string>%d jiné poznámky</string>
<string>... %d jiné poznámky ...</string>
</dict>
<key>NSStringLocalizedFormatKey</key>
<string>··· %#@NOTES@ ···</string>
</dict>
<key>followers_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@FOLLOWERS@</string>
<key>FOLLOWERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Seguidor</string>
<key>few</key>
<string>Followers</string>
<key>many</key>
<string>Followers</string>
<key>one</key>
<string>Seguidor</string>
<key>other</key>
<string>Sledují</string>
</dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@FOLLOWERS@</string>
</dict>
<key>reactions_count</key>
<dict>
@ -52,12 +52,12 @@
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Reakce</string>
<key>few</key>
<string>Reactions</string>
<key>many</key>
<string>Reactions</string>
<key>one</key>
<string>Reakce</string>
<key>other</key>
<string>Reakce</string>
</dict>
@ -72,12 +72,12 @@
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Relé</string>
<key>few</key>
<string>Relays</string>
<key>many</key>
<string>Relays</string>
<key>one</key>
<string>Relé</string>
<key>other</key>
<string>Relé</string>
</dict>
@ -85,45 +85,41 @@
<key>replying_to_one_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Odpověď na %@%#@OTHERS@</string>
<string>%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>few</key>
<string> &amp; %d others</string>
<key>many</key>
<string> &amp; %d others</string>
<key>one</key>
<string> a %d další</string>
<string>Odpověď na %2$@ a %1$d další</string>
<key>few</key>
<string>Odpověď na %2$@ a %1$d others</string>
<key>many</key>
<string>Odpověď na %2$@ a %1$d others</string>
<key>other</key>
<string> a %d další</string>
<key>zero</key>
<string></string>
<string>Odpověď na %2$@ a %1$d další</string>
</dict>
</dict>
<key>replying_to_two_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Odpovědět na %@, %@%#@OTHERS@</string>
<string>%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>few</key>
<string> &amp; %d others</string>
<key>many</key>
<string> &amp; %d others</string>
<key>one</key>
<string> a %d další</string>
<string>Odpovědět na %2$@, %3$@ &amp; %1$d další</string>
<key>few</key>
<string>Odpovědět na %2$@, %3$@ &amp; %1$d others</string>
<key>many</key>
<string>Odpovědět na %2$@, %3$@ &amp; %1$d others</string>
<key>other</key>
<string> a %d další</string>
<key>zero</key>
<string></string>
<string>Odpovědět na %2$@, %3$@ &amp; %1$d další</string>
</dict>
</dict>
<key>reposts_count</key>
@ -136,12 +132,12 @@
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Přesdílet</string>
<key>few</key>
<string>Reposts</string>
<key>many</key>
<string>Reposts</string>
<key>one</key>
<string>Přesdílet</string>
<key>other</key>
<string>Přesdílené </string>
</dict>
@ -156,12 +152,12 @@
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>@</string>
<key>one</key>
<string>%2$@ sat</string>
<key>few</key>
<string>%2$@ sats</string>
<key>many</key>
<string>%2$@ sats</string>
<key>one</key>
<string>%2$@ sat</string>
<key>other</key>
<string>%2$@ satů</string>
</dict>
@ -176,12 +172,12 @@
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Zap</string>
<key>few</key>
<string>Zaps</string>
<key>many</key>
<string>Zaps</string>
<key>one</key>
<string>Zap</string>
<key>other</key>
<string>Zapů</string>
</dict>

Binary file not shown.

Binary file not shown.

View File

@ -4,6 +4,8 @@
<dict>
<key>collapsed_event_view_other_notes</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@NOTES@</string>
<key>NOTES</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
@ -11,15 +13,15 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%d andere Notiz</string>
<string>... %d andere Notiz ...</string>
<key>other</key>
<string>%d andere Notizen</string>
<string>... %d andere Notizen ...</string>
</dict>
<key>NSStringLocalizedFormatKey</key>
<string>··· %#@NOTES@ ···</string>
</dict>
<key>followers_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@FOLLOWERS@</string>
<key>FOLLOWERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
@ -31,8 +33,6 @@
<key>other</key>
<string>Follower</string>
</dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@FOLLOWERS@</string>
</dict>
<key>reactions_count</key>
<dict>
@ -69,7 +69,7 @@
<key>replying_to_one_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Antwort an %@%#@OTHERS@</string>
<string>%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
@ -77,17 +77,15 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string> &amp; %d andere</string>
<string>Antwort an %2$@ &amp; %1$d andere</string>
<key>other</key>
<string> &amp; %d andere</string>
<key>zero</key>
<string></string>
<string>Antwort an %2$@ &amp; %1$d andere</string>
</dict>
</dict>
<key>replying_to_two_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Antwort an %@, %@%#@OTHERS@</string>
<string>%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
@ -95,11 +93,9 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string> &amp; %d andere</string>
<string>Antwort an %2$@, %3$@ &amp; %1$d andere</string>
<key>other</key>
<string> &amp; %d andere</string>
<key>zero</key>
<string></string>
<string>Antwort an %2$@, %3$@ &amp; %1$d andere</string>
</dict>
</dict>
<key>reposts_count</key>

Binary file not shown.

View File

@ -4,6 +4,8 @@
<dict>
<key>collapsed_event_view_other_notes</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@NOTES@</string>
<key>NOTES</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
@ -11,15 +13,15 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%d άλλη σημείωση</string>
<string>... %d άλλη σημείωση ...</string>
<key>other</key>
<string>%d άλλες σημειώσεις</string>
<string>... %d άλλες σημειώσεις ...</string>
</dict>
<key>NSStringLocalizedFormatKey</key>
<string>··· %#@NOTES@ ···</string>
</dict>
<key>followers_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@FOLLOWERS@</string>
<key>FOLLOWERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
@ -31,8 +33,6 @@
<key>other</key>
<string>Ακόλουθοι</string>
</dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@FOLLOWERS@</string>
</dict>
<key>reactions_count</key>
<dict>
@ -69,7 +69,7 @@
<key>replying_to_one_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Απάντηση προς %@%#@OTHERS@</string>
<string>%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
@ -77,17 +77,15 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string> &amp; %d άλλον</string>
<string>Replying to %2$@ &amp; %1$d other</string>
<key>other</key>
<string> &amp; %d άλλους</string>
<key>zero</key>
<string></string>
<string>Replying to %2$@ &amp; %1$d others</string>
</dict>
</dict>
<key>replying_to_two_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Απάντηση προς %@, %@%#@OTHERS@</string>
<string>%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
@ -95,11 +93,9 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string> &amp; %d άλλον</string>
<string>Απάντηση προς %2$@, %3$@ &amp; %1$d άλλον</string>
<key>other</key>
<string> &amp; %d άλλους</string>
<key>zero</key>
<string></string>
<string>Απάντηση προς %2$@, %3$@ &amp; %1$d άλλους</string>
</dict>
</dict>
<key>reposts_count</key>

Binary file not shown.

View File

@ -34,6 +34,54 @@
<string>Followers</string>
</dict>
</dict>
<key>reacted_tagged_in_3</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@REACTED@</string>
<key>REACTED</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%2$@ and %1$d other reacted to a post you were tagged in</string>
<key>other</key>
<string>%2$@ and %1$d others reacted to a post you were tagged in</string>
</dict>
</dict>
<key>reacted_your_post_3</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@REACTED@</string>
<key>REACTED</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%2$@ and %1$d other reacted to your post</string>
<key>other</key>
<string>%2$@ and %1$d others reacted to your post</string>
</dict>
</dict>
<key>reacted_your_profile_3</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@REACTED@</string>
<key>REACTED</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%2$@ and %1$d other reacted to your profile</string>
<key>other</key>
<string>%2$@ and %1$d others reacted to your profile</string>
</dict>
</dict>
<key>reactions_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
@ -66,22 +114,6 @@
<string>Relays</string>
</dict>
</dict>
<key>replying_to_one_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Replying to %2$@ &amp; %1$d other</string>
<key>other</key>
<string>Replying to %2$@ &amp; %1$d others</string>
</dict>
</dict>
<key>replying_to_two_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
@ -98,6 +130,54 @@
<string>Replying to %2$@, %3$@ &amp; %1$d others</string>
</dict>
</dict>
<key>reposted_tagged_in_3</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@REPOSTED@</string>
<key>REPOSTED</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%2$@ and %1$d other reposted a post you were tagged in</string>
<key>other</key>
<string>%2$@ and %1$d others reposted a post you were tagged in</string>
</dict>
</dict>
<key>reposted_your_post_3</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@REPOSTED@</string>
<key>REPOSTED</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%2$@ and %1$d other reposted your post</string>
<key>other</key>
<string>%2$@ and %1$d others reposted your post</string>
</dict>
</dict>
<key>reposted_your_profile_3</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@REPOSTED@</string>
<key>REPOSTED</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%2$@ and %1$d other reposted your profile</string>
<key>other</key>
<string>%2$@ and %1$d others reposted your profile</string>
</dict>
</dict>
<key>reposts_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
@ -130,6 +210,54 @@
<string>%2$@ sats</string>
</dict>
</dict>
<key>zapped_tagged_in_3</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@ZAPPED@</string>
<key>ZAPPED</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%2$@ and %1$d other zapped a post you were tagged in</string>
<key>other</key>
<string>%2$@ and %1$d others zapped a post you were tagged in</string>
</dict>
</dict>
<key>zapped_your_post_3</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@ZAPPED@</string>
<key>ZAPPED</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%2$@ and %1$d other zapped your post</string>
<key>other</key>
<string>%2$@ and %1$d others zapped your post</string>
</dict>
</dict>
<key>zapped_your_profile_3</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@ZAPPED@</string>
<key>ZAPPED</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%2$@ and %1$d other zapped your profile</string>
<key>other</key>
<string>%2$@ and %1$d others zapped your profile</string>
</dict>
</dict>
<key>zaps_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>

View File

@ -32,6 +32,11 @@
<tool tool-id="com.apple.dt.xcode" tool-name="Xcode" tool-version="14.2" build-num="14C18"/>
</header>
<body>
<trans-unit id="%@" xml:space="preserve">
<source>%@</source>
<target>%@</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%@ %@" xml:space="preserve">
<source>%@ %@</source>
<target>%@ %@</target>
@ -58,6 +63,11 @@ Sentence composed of 2 variables to describe how many people are following a use
<target>%@. Tip your friend's posts and stack sats with Bitcoin⚡, the native currency of the internet.</target>
<note>Explanation of what can be done by users to earn money. There is a heading that precedes this explanation which is a variable to this string.</note>
</trans-unit>
<trans-unit id="%lld" xml:space="preserve">
<source>%lld</source>
<target>%lld</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="%lld/%lld" xml:space="preserve">
<source>%lld/%lld</source>
<target>%lld/%lld</target>
@ -73,15 +83,10 @@ Sentence composed of 2 variables to describe how many people are following a use
<target>'%@' is an invalid NIP-05 identifier. It should look like an email.</target>
<note>Description of why the nip05 identifier is invalid.</note>
</trans-unit>
<trans-unit id="(Profile.displayName(profile: profile, pubkey: whos))'s Followers" xml:space="preserve">
<source>(Profile.displayName(profile: profile, pubkey: whos))'s Followers</source>
<target>(Profile.displayName(profile: profile, pubkey: whos))'s Followers</target>
<note>Navigation bar title for view that shows who is following a user.</note>
</trans-unit>
<trans-unit id="(who) following" xml:space="preserve">
<source>(who) following</source>
<target>(who) following</target>
<note>Navigation bar title for view that shows who a user is following.</note>
<trans-unit id="??" xml:space="preserve">
<source>??</source>
<target>??</target>
<note>No comment provided by engineer.</note>
</trans-unit>
<trans-unit id="API Key (optional)" xml:space="preserve">
<source>API Key (optional)</source>
@ -129,6 +134,11 @@ Sentence composed of 2 variables to describe how many people are following a use
<note>Button to add recommended relay server.
Button to confirm adding user inputted relay.</note>
</trans-unit>
<trans-unit id="Add Bookmark" xml:space="preserve">
<source>Add Bookmark</source>
<target>Add Bookmark</target>
<note>Context menu option for adding a note bookmark.</note>
</trans-unit>
<trans-unit id="Add Relay" xml:space="preserve">
<source>Add Relay</source>
<target>Add Relay</target>
@ -144,6 +154,11 @@ Sentence composed of 2 variables to describe how many people are following a use
<target>Admin</target>
<note>Label to display relay contact user.</note>
</trans-unit>
<trans-unit id="Anonymous" xml:space="preserve">
<source>Anonymous</source>
<target>Anonymous</target>
<note>Picker option to indicate that a zap should be sent anonymously and not identify the user as who sent it.</note>
</trans-unit>
<trans-unit id="Any" xml:space="preserve">
<source>Any</source>
<target>Any</target>
@ -154,6 +169,11 @@ Sentence composed of 2 variables to describe how many people are following a use
<target>Are you sure you want to repost this?</target>
<note>Alert message to ask if user wants to repost a post.</note>
</trans-unit>
<trans-unit id="Awesome post!" xml:space="preserve">
<source>Awesome post!</source>
<target>Awesome post!</target>
<note>Placeholder text for a comment to send as part of a zap to the user.</note>
</trans-unit>
<trans-unit id="Banner Image" xml:space="preserve">
<source>Banner Image</source>
<target>Banner Image</target>
@ -211,6 +231,12 @@ Sentence composed of 2 variables to describe how many people are following a use
<target>Blue Wallet</target>
<note>Dropdown option label for Lightning wallet, Blue Wallet.</note>
</trans-unit>
<trans-unit id="Bookmarks" xml:space="preserve">
<source>Bookmarks</source>
<target>Bookmarks</target>
<note>Sidebar menu label for Bookmarks view.
Title of bookmarks view</note>
</trans-unit>
<trans-unit id="Boosts" xml:space="preserve">
<source>Boosts</source>
<target>Boosts</target>
@ -247,11 +273,21 @@ Sentence composed of 2 variables to describe how many people are following a use
<target>Clear</target>
<note>Button for clearing cached data.</note>
</trans-unit>
<trans-unit id="Clear All" xml:space="preserve">
<source>Clear All</source>
<target>Clear All</target>
<note>Button for clearing bookmarks data.</note>
</trans-unit>
<trans-unit id="Clear Cache" xml:space="preserve">
<source>Clear Cache</source>
<target>Clear Cache</target>
<note>Section title for clearing cached data.</note>
</trans-unit>
<trans-unit id="Comment" xml:space="preserve">
<source>Comment</source>
<target>Comment</target>
<note>Header text to indicate that the text field below it is a comment that will be used to send as part of a zap to the user.</note>
</trans-unit>
<trans-unit id="Contact" xml:space="preserve">
<source>Contact</source>
<target>Contact</target>
@ -348,6 +384,11 @@ Sentence composed of 2 variables to describe how many people are following a use
<target>Custom</target>
<note>Dropdown option for selecting a custom translation server.</note>
</trans-unit>
<trans-unit id="Custom Zap Amount" xml:space="preserve">
<source>Custom Zap Amount</source>
<target>Custom Zap Amount</target>
<note>Header text to indicate that the text field below it is to enter a custom zap amount.</note>
</trans-unit>
<trans-unit id="DMs" xml:space="preserve">
<source>DMs</source>
<target>DMs</target>
@ -470,12 +511,12 @@ Sentence composed of 2 variables to describe how many people are following a use
<trans-unit id="Followers" xml:space="preserve">
<source>Followers</source>
<target>Followers</target>
<note>Label describing followers of a user.</note>
<note>Navigation bar title for view that shows who is following a user.</note>
</trans-unit>
<trans-unit id="Following" xml:space="preserve">
<source>Following</source>
<target>Following</target>
<note>Part of a larger sentence to describe how many profiles a user is following.</note>
<note>Navigation bar title for view that shows who a user is following.</note>
</trans-unit>
<trans-unit id="Following..." xml:space="preserve">
<source>Following...</source>
@ -573,6 +614,11 @@ Sentence composed of 2 variables to describe how many people are following a use
<target>Like</target>
<note>Accessibility Label for Like button</note>
</trans-unit>
<trans-unit id="Load %lld more" xml:space="preserve">
<source>Load %lld more</source>
<target>Load %lld more</target>
<note>Button text for loading more events, where the variable is the number of events.</note>
</trans-unit>
<trans-unit id="Local authentication to access private key" xml:space="preserve">
<source>Local authentication to access private key</source>
<target>Local authentication to access private key</target>
@ -621,6 +667,11 @@ Sentence composed of 2 variables to describe how many people are following a use
<target>No block list found, create a new one? This will overwrite any previous block lists.</target>
<note>Alert message prompt that asks if the user wants to create a new block list, overwriting previous block lists.</note>
</trans-unit>
<trans-unit id="Non-Zap" xml:space="preserve">
<source>Non-Zap</source>
<target>Non-Zap</target>
<note>Picker option to indicate that sats should be sent to the user's wallet as a regular Lightning payment, not as a zap.</note>
</trans-unit>
<trans-unit id="None" xml:space="preserve">
<source>None</source>
<target>None</target>
@ -676,6 +727,11 @@ Sentence composed of 2 variables to describe how many people are following a use
<target>Plan</target>
<note>Prompt selection of DeepL subscription plan to perform machine translations on notes</note>
</trans-unit>
<trans-unit id="Please choose relays from the list below to filter the current feed:" xml:space="preserve">
<source>Please choose relays from the list below to filter the current feed:</source>
<target>Please choose relays from the list below to filter the current feed:</target>
<note>Instructions on how to filter a specific timeline feed by choosing relay servers to filter on.</note>
</trans-unit>
<trans-unit id="Post" xml:space="preserve">
<source>Post</source>
<target>Post</target>
@ -723,6 +779,11 @@ Label for filter for seeing your posts and replies (instead of only your posts).
<target>Profile Picture</target>
<note>Label for Profile Picture section of user profile form.</note>
</trans-unit>
<trans-unit id="Public" xml:space="preserve">
<source>Public</source>
<target>Public</target>
<note>Picker option to indicate that a zap should be sent publicly and identify the user as who sent it.</note>
</trans-unit>
<trans-unit id="Public Account ID" xml:space="preserve">
<source>Public Account ID</source>
<target>Public Account ID</target>
@ -774,6 +835,11 @@ Label for filter for seeing your posts and replies (instead of only your posts).
<target>Relays have been notified and clients will be able to use this information to filter content. Thank you!</target>
<note>Description of what was done as a result of sending a report to relay servers.</note>
</trans-unit>
<trans-unit id="Remove Bookmark" xml:space="preserve">
<source>Remove Bookmark</source>
<target>Remove Bookmark</target>
<note>Context menu option for removing a note bookmark.</note>
</trans-unit>
<trans-unit id="Remove all" xml:space="preserve">
<source>Remove all</source>
<target>Remove all</target>
@ -986,9 +1052,9 @@ Label for filter for seeing your posts and replies (instead of only your posts).
<target>This is an old-style nostr key. We're not sure if it's a pubkey or private key. Please toggle the button below if this a public key.</target>
<note>Warning that the inputted account key for login is an old-style and asking user to verify if it is a public key.</note>
</trans-unit>
<trans-unit id="This is your account ID, you can give this to your friends so that they can follow you. Click to copy." xml:space="preserve">
<source>This is your account ID, you can give this to your friends so that they can follow you. Click to copy.</source>
<target>This is your account ID, you can give this to your friends so that they can follow you. Click to copy.</target>
<trans-unit id="This is your account ID, you can give this to your friends so that they can follow you. Tap to copy." xml:space="preserve">
<source>This is your account ID, you can give this to your friends so that they can follow you. Tap to copy.</source>
<target>This is your account ID, you can give this to your friends so that they can follow you. Tap to copy.</target>
<note>Label to describe that a public key is the user's account ID and what they can do with it.</note>
</trans-unit>
<trans-unit id="This is your secret account key. You need this to access your account. Don't share this with anyone! Save it in a password manager and keep it safe!" xml:space="preserve">
@ -1001,11 +1067,6 @@ Label for filter for seeing your posts and replies (instead of only your posts).
<target>Thread</target>
<note>Navigation bar title for note thread.</note>
</trans-unit>
<trans-unit id="To filter your %@ feed, please choose applicable relays from the list below:" xml:space="preserve">
<source>To filter your %@ feed, please choose applicable relays from the list below:</source>
<target>To filter your %@ feed, please choose applicable relays from the list below:</target>
<note>Instructions on how to filter a specific timeline feed by choosing relay servers to filter on.</note>
</trans-unit>
<trans-unit id="Translate Note" xml:space="preserve">
<source>Translate Note</source>
<target>Translate Note</target>
@ -1123,6 +1184,11 @@ Label for filter for seeing your posts and replies (instead of only your posts).
<target>Yes, Post with Private Key</target>
<note>Button to proceed with posting a note even though it looks like they might be posting a private key.</note>
</trans-unit>
<trans-unit id="You have no bookmarks yet, add them in the context menu" xml:space="preserve">
<source>You have no bookmarks yet, add them in the context menu</source>
<target>You have no bookmarks yet, add them in the context menu</target>
<note>Text indicating that there are no bookmarks to be viewed</note>
</trans-unit>
<trans-unit id="Your Name" xml:space="preserve">
<source>Your Name</source>
<target>Your Name</target>
@ -1136,7 +1202,28 @@ Label for filter for seeing your posts and replies (instead of only your posts).
<trans-unit id="Zap" xml:space="preserve">
<source>Zap</source>
<target>Zap</target>
<note>Accessibility label for zap button</note>
<note>Accessibility label for zap button
Button to send a zap.</note>
</trans-unit>
<trans-unit id="Zap Amount" xml:space="preserve">
<source>Zap Amount</source>
<target>Zap Amount</target>
<note>Title of picker that allows selection of predefined amounts to zap.</note>
</trans-unit>
<trans-unit id="Zap Amount in sats" xml:space="preserve">
<source>Zap Amount in sats</source>
<target>Zap Amount in sats</target>
<note>Header text to indicate that the picker below it is to choose a pre-defined amount of sats to zap.</note>
</trans-unit>
<trans-unit id="Zap Type" xml:space="preserve">
<source>Zap Type</source>
<target>Zap Type</target>
<note>Header text to indicate that the picker below it is to choose the type of zap to send.</note>
</trans-unit>
<trans-unit id="Zapping..." xml:space="preserve">
<source>Zapping...</source>
<target>Zapping...</target>
<note>Text to indicate that the app is in the process of sending a zap.</note>
</trans-unit>
<trans-unit id="Zaps" xml:space="preserve">
<source>Zaps</source>
@ -1188,6 +1275,66 @@ Label for filter for seeing your posts and replies (instead of only your posts).
<target>optional</target>
<note>Label indicating that a form input is optional.</note>
</trans-unit>
<trans-unit id="reacted_tagged_in_1" xml:space="preserve">
<source>%@ reacted to a post you were tagged in</source>
<target>%@ reacted to a post you were tagged in</target>
<note>Notification that a user reacted to a post that the current user was tagged in</note>
</trans-unit>
<trans-unit id="reacted_tagged_in_2" xml:space="preserve">
<source>%@ and %@ reacted to a post you were tagged in</source>
<target>%@ and %@ reacted to a post you were tagged in</target>
<note>Notification that 2 users reacted to a post that the current user was tagged in</note>
</trans-unit>
<trans-unit id="reacted_your_post_1" xml:space="preserve">
<source>%@ reacted to your post</source>
<target>%@ reacted to your post</target>
<note>Notification that a user reacted to the current user's post</note>
</trans-unit>
<trans-unit id="reacted_your_post_2" xml:space="preserve">
<source>%@ and %@ reacted to your post</source>
<target>%@ and %@ reacted to your post</target>
<note>Notification that 2 users reacted to the current user's profile</note>
</trans-unit>
<trans-unit id="reacted_your_profile_1" xml:space="preserve">
<source>%@ reacted to your profile</source>
<target>%@ reacted to your profile</target>
<note>Notification that a user reacted to the current user's profile</note>
</trans-unit>
<trans-unit id="reacted_your_profile_2" xml:space="preserve">
<source>%@ and %@ reacted to your profile</source>
<target>%@ and %@ reacted to your profile</target>
<note>Notification that 2 users reacted to the current user's profile</note>
</trans-unit>
<trans-unit id="reposted_tagged_in_1" xml:space="preserve">
<source>%@ reposted a post you were tagged in</source>
<target>%@ reposted a post you were tagged in</target>
<note>Notification that a user reposted a post that the current user was tagged in</note>
</trans-unit>
<trans-unit id="reposted_tagged_in_2" xml:space="preserve">
<source>%@ and %@ reposted a post you were tagged in</source>
<target>%@ and %@ reposted a post you were tagged in</target>
<note>Notification that 2 users reposted a post that the current user was tagged in</note>
</trans-unit>
<trans-unit id="reposted_your_post_1" xml:space="preserve">
<source>%@ reposted your post</source>
<target>%@ reposted your post</target>
<note>Notification that a user reposted the current user's post</note>
</trans-unit>
<trans-unit id="reposted_your_post_2" xml:space="preserve">
<source>%@ and %@ reposted your post</source>
<target>%@ and %@ reposted your post</target>
<note>Notification that 2 users reposted the current user's post</note>
</trans-unit>
<trans-unit id="reposted_your_profile_1" xml:space="preserve">
<source>%@ reposted your profile</source>
<target>%@ reposted your profile</target>
<note>Notification that a user reposted the current user's profile</note>
</trans-unit>
<trans-unit id="reposted_your_profile_2" xml:space="preserve">
<source>%@ and %@ reposted your profile</source>
<target>%@ and %@ reposted your profile</target>
<note>Notification that 2 users reposted the current user's profile</note>
</trans-unit>
<trans-unit id="satoshi" xml:space="preserve">
<source>satoshi</source>
<target>satoshi</target>
@ -1203,6 +1350,36 @@ Label for filter for seeing your posts and replies (instead of only your posts).
<target>you</target>
<note>You, in this context, is the person who controls their own social network. You is used in the context of a larger sentence that welcomes the reader to the social network that they control themself.</note>
</trans-unit>
<trans-unit id="zapped_tagged_in_1" xml:space="preserve">
<source>%@ zapped a post you were tagged in</source>
<target>%@ zapped a post you were tagged in</target>
<note>Notification that a user zapped a post that the current user was tagged in</note>
</trans-unit>
<trans-unit id="zapped_tagged_in_2" xml:space="preserve">
<source>%@ and %@ zapped a post you were tagged in</source>
<target>%@ and %@ zapped a post you were tagged in</target>
<note>Notification that 2 users zapped a post that the current user was tagged in</note>
</trans-unit>
<trans-unit id="zapped_your_post_1" xml:space="preserve">
<source>%@ zapped your post</source>
<target>%@ zapped your post</target>
<note>Notification that a user zapped the current user's post</note>
</trans-unit>
<trans-unit id="zapped_your_post_2" xml:space="preserve">
<source>%@ and %@ zapped your post</source>
<target>%@ and %@ zapped your post</target>
<note>Notification that 2 users zapped the current user's post</note>
</trans-unit>
<trans-unit id="zapped_your_profile_1" xml:space="preserve">
<source>%@ zapped your profile</source>
<target>%@ zapped your profile</target>
<note>Notification that a user zapped the current user's profile</note>
</trans-unit>
<trans-unit id="zapped_your_profile_2" xml:space="preserve">
<source>%@ and %@ zapped your profile</source>
<target>%@ and %@ zapped your profile</target>
<note>Notification that 2 users zapped the current user's profile</note>
</trans-unit>
<trans-unit id="⚡️ %@" xml:space="preserve">
<source>⚡️ %@</source>
<target>⚡️ %@</target>
@ -1245,6 +1422,51 @@ Label for filter for seeing your posts and replies (instead of only your posts).
<target>%#@FOLLOWERS@</target>
<note/>
</trans-unit>
<trans-unit id="/reacted_tagged_in_3:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
<source>%#@REACTED@</source>
<target>%#@REACTED@</target>
<note/>
</trans-unit>
<trans-unit id="/reacted_tagged_in_3:dict/REACTED:dict/one:dict/:string" xml:space="preserve">
<source>%2$@ and %1$d other reacted to a post you were tagged in</source>
<target>%2$@ and %1$d other reacted to a post you were tagged in</target>
<note/>
</trans-unit>
<trans-unit id="/reacted_tagged_in_3:dict/REACTED:dict/other:dict/:string" xml:space="preserve">
<source>%2$@ and %1$d others reacted to a post you were tagged in</source>
<target>%2$@ and %1$d others reacted to a post you were tagged in</target>
<note/>
</trans-unit>
<trans-unit id="/reacted_your_post_3:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
<source>%#@REACTED@</source>
<target>%#@REACTED@</target>
<note/>
</trans-unit>
<trans-unit id="/reacted_your_post_3:dict/REACTED:dict/one:dict/:string" xml:space="preserve">
<source>%2$@ and %1$d other reacted to your post</source>
<target>%2$@ and %1$d other reacted to your post</target>
<note/>
</trans-unit>
<trans-unit id="/reacted_your_post_3:dict/REACTED:dict/other:dict/:string" xml:space="preserve">
<source>%2$@ and %1$d others reacted to your post</source>
<target>%2$@ and %1$d others reacted to your post</target>
<note/>
</trans-unit>
<trans-unit id="/reacted_your_profile_3:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
<source>%#@REACTED@</source>
<target>%#@REACTED@</target>
<note/>
</trans-unit>
<trans-unit id="/reacted_your_profile_3:dict/REACTED:dict/one:dict/:string" xml:space="preserve">
<source>%2$@ and %1$d other reacted to your profile</source>
<target>%2$@ and %1$d other reacted to your profile</target>
<note/>
</trans-unit>
<trans-unit id="/reacted_your_profile_3:dict/REACTED:dict/other:dict/:string" xml:space="preserve">
<source>%2$@ and %1$d others reacted to your profile</source>
<target>%2$@ and %1$d others reacted to your profile</target>
<note/>
</trans-unit>
<trans-unit id="/reactions_count:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
<source>%#@REACTIONS@</source>
<target>%#@REACTIONS@</target>
@ -1275,21 +1497,6 @@ Label for filter for seeing your posts and replies (instead of only your posts).
<target>Relays</target>
<note/>
</trans-unit>
<trans-unit id="/replying_to_one_and_others:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
<source>%#@OTHERS@</source>
<target>%#@OTHERS@</target>
<note/>
</trans-unit>
<trans-unit id="/replying_to_one_and_others:dict/OTHERS:dict/one:dict/:string" xml:space="preserve">
<source>Replying to %2$@ &amp; %1$d other</source>
<target>Replying to %2$@ &amp; %1$d other</target>
<note/>
</trans-unit>
<trans-unit id="/replying_to_one_and_others:dict/OTHERS:dict/other:dict/:string" xml:space="preserve">
<source>Replying to %2$@ &amp; %1$d others</source>
<target>Replying to %2$@ &amp; %1$d others</target>
<note/>
</trans-unit>
<trans-unit id="/replying_to_two_and_others:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
<source>%#@OTHERS@</source>
<target>%#@OTHERS@</target>
@ -1305,6 +1512,51 @@ Label for filter for seeing your posts and replies (instead of only your posts).
<target>Replying to %2$@, %3$@ &amp; %1$d others</target>
<note/>
</trans-unit>
<trans-unit id="/reposted_tagged_in_3:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
<source>%#@REPOSTED@</source>
<target>%#@REPOSTED@</target>
<note/>
</trans-unit>
<trans-unit id="/reposted_tagged_in_3:dict/REPOSTED:dict/one:dict/:string" xml:space="preserve">
<source>%2$@ and %1$d other reposted a post you were tagged in</source>
<target>%2$@ and %1$d other reposted a post you were tagged in</target>
<note/>
</trans-unit>
<trans-unit id="/reposted_tagged_in_3:dict/REPOSTED:dict/other:dict/:string" xml:space="preserve">
<source>%2$@ and %1$d others reposted a post you were tagged in</source>
<target>%2$@ and %1$d others reposted a post you were tagged in</target>
<note/>
</trans-unit>
<trans-unit id="/reposted_your_post_3:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
<source>%#@REPOSTED@</source>
<target>%#@REPOSTED@</target>
<note/>
</trans-unit>
<trans-unit id="/reposted_your_post_3:dict/REPOSTED:dict/one:dict/:string" xml:space="preserve">
<source>%2$@ and %1$d other reposted your post</source>
<target>%2$@ and %1$d other reposted your post</target>
<note/>
</trans-unit>
<trans-unit id="/reposted_your_post_3:dict/REPOSTED:dict/other:dict/:string" xml:space="preserve">
<source>%2$@ and %1$d others reposted your post</source>
<target>%2$@ and %1$d others reposted your post</target>
<note/>
</trans-unit>
<trans-unit id="/reposted_your_profile_3:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
<source>%#@REPOSTED@</source>
<target>%#@REPOSTED@</target>
<note/>
</trans-unit>
<trans-unit id="/reposted_your_profile_3:dict/REPOSTED:dict/one:dict/:string" xml:space="preserve">
<source>%2$@ and %1$d other reposted your profile</source>
<target>%2$@ and %1$d other reposted your profile</target>
<note/>
</trans-unit>
<trans-unit id="/reposted_your_profile_3:dict/REPOSTED:dict/other:dict/:string" xml:space="preserve">
<source>%2$@ and %1$d others reposted your profile</source>
<target>%2$@ and %1$d others reposted your profile</target>
<note/>
</trans-unit>
<trans-unit id="/reposts_count:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
<source>%#@REPOSTS@</source>
<target>%#@REPOSTS@</target>
@ -1335,6 +1587,51 @@ Label for filter for seeing your posts and replies (instead of only your posts).
<target>%2$@ sats</target>
<note/>
</trans-unit>
<trans-unit id="/zapped_tagged_in_3:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
<source>%#@ZAPPED@</source>
<target>%#@ZAPPED@</target>
<note/>
</trans-unit>
<trans-unit id="/zapped_tagged_in_3:dict/ZAPPED:dict/one:dict/:string" xml:space="preserve">
<source>%2$@ and %1$d other zapped a post you were tagged in</source>
<target>%2$@ and %1$d other zapped a post you were tagged in</target>
<note/>
</trans-unit>
<trans-unit id="/zapped_tagged_in_3:dict/ZAPPED:dict/other:dict/:string" xml:space="preserve">
<source>%2$@ and %1$d others zapped a post you were tagged in</source>
<target>%2$@ and %1$d others zapped a post you were tagged in</target>
<note/>
</trans-unit>
<trans-unit id="/zapped_your_post_3:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
<source>%#@ZAPPED@</source>
<target>%#@ZAPPED@</target>
<note/>
</trans-unit>
<trans-unit id="/zapped_your_post_3:dict/ZAPPED:dict/one:dict/:string" xml:space="preserve">
<source>%2$@ and %1$d other zapped your post</source>
<target>%2$@ and %1$d other zapped your post</target>
<note/>
</trans-unit>
<trans-unit id="/zapped_your_post_3:dict/ZAPPED:dict/other:dict/:string" xml:space="preserve">
<source>%2$@ and %1$d others zapped your post</source>
<target>%2$@ and %1$d others zapped your post</target>
<note/>
</trans-unit>
<trans-unit id="/zapped_your_profile_3:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
<source>%#@ZAPPED@</source>
<target>%#@ZAPPED@</target>
<note/>
</trans-unit>
<trans-unit id="/zapped_your_profile_3:dict/ZAPPED:dict/one:dict/:string" xml:space="preserve">
<source>%2$@ and %1$d other zapped your profile</source>
<target>%2$@ and %1$d other zapped your profile</target>
<note/>
</trans-unit>
<trans-unit id="/zapped_your_profile_3:dict/ZAPPED:dict/other:dict/:string" xml:space="preserve">
<source>%2$@ and %1$d others zapped your profile</source>
<target>%2$@ and %1$d others zapped your profile</target>
<note/>
</trans-unit>
<trans-unit id="/zaps_count:dict/NSStringLocalizedFormatKey:dict/:string" xml:space="preserve">
<source>%#@ZAPS@</source>
<target>%#@ZAPS@</target>

View File

@ -34,6 +34,54 @@
<string>Followers</string>
</dict>
</dict>
<key>reacted_tagged_in_3</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@REACTED@</string>
<key>REACTED</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%2$@ and %1$d other reacted to a post you were tagged in</string>
<key>other</key>
<string>%2$@ and %1$d others reacted to a post you were tagged in</string>
</dict>
</dict>
<key>reacted_your_post_3</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@REACTED@</string>
<key>REACTED</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%2$@ and %1$d other reacted to your post</string>
<key>other</key>
<string>%2$@ and %1$d others reacted to your post</string>
</dict>
</dict>
<key>reacted_your_profile_3</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@REACTED@</string>
<key>REACTED</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%2$@ and %1$d other reacted to your profile</string>
<key>other</key>
<string>%2$@ and %1$d others reacted to your profile</string>
</dict>
</dict>
<key>reactions_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
@ -66,22 +114,6 @@
<string>Relays</string>
</dict>
</dict>
<key>replying_to_one_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>Replying to %2$@ &amp; %1$d other</string>
<key>other</key>
<string>Replying to %2$@ &amp; %1$d others</string>
</dict>
</dict>
<key>replying_to_two_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
@ -98,6 +130,54 @@
<string>Replying to %2$@, %3$@ &amp; %1$d others</string>
</dict>
</dict>
<key>reposted_tagged_in_3</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@REPOSTED@</string>
<key>REPOSTED</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%2$@ and %1$d other reposted a post you were tagged in</string>
<key>other</key>
<string>%2$@ and %1$d others reposted a post you were tagged in</string>
</dict>
</dict>
<key>reposted_your_post_3</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@REPOSTED@</string>
<key>REPOSTED</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%2$@ and %1$d other reposted your post</string>
<key>other</key>
<string>%2$@ and %1$d others reposted your post</string>
</dict>
</dict>
<key>reposted_your_profile_3</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@REPOSTED@</string>
<key>REPOSTED</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%2$@ and %1$d other reposted your profile</string>
<key>other</key>
<string>%2$@ and %1$d others reposted your profile</string>
</dict>
</dict>
<key>reposts_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
@ -130,6 +210,54 @@
<string>%2$@ sats</string>
</dict>
</dict>
<key>zapped_tagged_in_3</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@ZAPPED@</string>
<key>ZAPPED</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%2$@ and %1$d other zapped a post you were tagged in</string>
<key>other</key>
<string>%2$@ and %1$d others zapped a post you were tagged in</string>
</dict>
</dict>
<key>zapped_your_post_3</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@ZAPPED@</string>
<key>ZAPPED</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%2$@ and %1$d other zapped your post</string>
<key>other</key>
<string>%2$@ and %1$d others zapped your post</string>
</dict>
</dict>
<key>zapped_your_profile_3</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@ZAPPED@</string>
<key>ZAPPED</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%2$@ and %1$d other zapped your profile</string>
<key>other</key>
<string>%2$@ and %1$d others zapped your profile</string>
</dict>
</dict>
<key>zaps_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>

Binary file not shown.

View File

@ -4,6 +4,8 @@
<dict>
<key>collapsed_event_view_other_notes</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@NOTES@</string>
<key>NOTES</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
@ -11,15 +13,17 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%d otra nota</string>
<string>... %d otra nota ...</string>
<key>many</key>
<string>... %d otras notas ...</string>
<key>other</key>
<string>%d otras notas</string>
<string>... %d otras notas...</string>
</dict>
<key>NSStringLocalizedFormatKey</key>
<string>··· %#@NOTES@ ···</string>
</dict>
<key>followers_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@FOLLOWERS@</string>
<key>FOLLOWERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
@ -28,11 +32,11 @@
<string>d</string>
<key>one</key>
<string>Seguidor</string>
<key>many</key>
<string>Seguidores</string>
<key>other</key>
<string>Seguidores</string>
</dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@FOLLOWERS@</string>
</dict>
<key>reactions_count</key>
<dict>
@ -46,6 +50,8 @@
<string>d</string>
<key>one</key>
<string>Reacción</string>
<key>many</key>
<string>Reacciones</string>
<key>other</key>
<string>Reacciones</string>
</dict>
@ -62,6 +68,8 @@
<string>d</string>
<key>one</key>
<string>Relé</string>
<key>many</key>
<string>Relés</string>
<key>other</key>
<string>Relés</string>
</dict>
@ -69,7 +77,7 @@
<key>replying_to_one_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Respondiendo a %@%#@OTHERS@</string>
<string>%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
@ -77,17 +85,17 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string> y %d otro</string>
<string>Respondiendo a %2$@ y %1$d otro</string>
<key>many</key>
<string>Respondiendo a %2$@ y %1$d otros</string>
<key>other</key>
<string> y %d otros</string>
<key>zero</key>
<string></string>
<string>Respondiendo a %2$@ y %1$d otros</string>
</dict>
</dict>
<key>replying_to_two_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Respondiendo a %@, %@%#@OTHERS@</string>
<string>%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
@ -95,11 +103,11 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string> y %d otro</string>
<string>Respondiendo a %2$@, %3$@ y %1$d otro</string>
<key>many</key>
<string>Respondiendo a %2$@, %3$@ y %1$d otros</string>
<key>other</key>
<string> y %d otros</string>
<key>zero</key>
<string></string>
<string>Respondiendo a %2$@, %3$@ y %1$d otros</string>
</dict>
</dict>
<key>reposts_count</key>
@ -114,6 +122,8 @@
<string>d</string>
<key>one</key>
<string>Republicación</string>
<key>many</key>
<string>Republicaciones</string>
<key>other</key>
<string>Republicaciones</string>
</dict>
@ -130,6 +140,8 @@
<string>@</string>
<key>one</key>
<string>%2$@ sat</string>
<key>many</key>
<string>%2$@ sats</string>
<key>other</key>
<string>%2$@ sats</string>
</dict>
@ -146,6 +158,8 @@
<string>d</string>
<key>one</key>
<string>Zap</string>
<key>many</key>
<string>Zaps</string>
<key>other</key>
<string>Zaps</string>
</dict>

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -4,6 +4,8 @@
<dict>
<key>collapsed_event_view_other_notes</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@NOTES@</string>
<key>NOTES</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
@ -11,15 +13,17 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%d autre note</string>
<string>... %d autre note ...</string>
<key>many</key>
<string>... %d autres notes ...</string>
<key>other</key>
<string>%d autres notes</string>
<string>... %d autres notes ...</string>
</dict>
<key>NSStringLocalizedFormatKey</key>
<string>··· %#@NOTES@ ···</string>
</dict>
<key>followers_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@FOLLOWERS@</string>
<key>FOLLOWERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
@ -28,11 +32,11 @@
<string>d</string>
<key>one</key>
<string>Abonné</string>
<key>many</key>
<string>Abonnés</string>
<key>other</key>
<string>Abonnés</string>
</dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@FOLLOWERS@</string>
</dict>
<key>reactions_count</key>
<dict>
@ -46,6 +50,8 @@
<string>d</string>
<key>one</key>
<string>Réaction</string>
<key>many</key>
<string>Réactions</string>
<key>other</key>
<string>Réactions</string>
</dict>
@ -62,6 +68,8 @@
<string>d</string>
<key>one</key>
<string>Relais</string>
<key>many</key>
<string>Relais</string>
<key>other</key>
<string>Relais</string>
</dict>
@ -69,7 +77,7 @@
<key>replying_to_one_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Réponse à %@%#@OTHERS@</string>
<string>%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
@ -77,17 +85,17 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string> &amp; %d autre</string>
<string>Réponse à %2$@ &amp; %1$d autre</string>
<key>many</key>
<string>Réponse à %2$@ &amp; %1$d autres</string>
<key>other</key>
<string> &amp; %d autres</string>
<key>zero</key>
<string></string>
<string>Réponse à %2$@ &amp; %1$d autres</string>
</dict>
</dict>
<key>replying_to_two_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Réponse à %@, %@%#@OTHERS@</string>
<string>%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
@ -95,11 +103,11 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string> &amp; %d autre</string>
<string>Réponse à %2$@, %3$@ &amp; %1$d autre</string>
<key>many</key>
<string>Réponse à %2$@, %3$@ &amp; %1$d autres</string>
<key>other</key>
<string> &amp; %d autres</string>
<key>zero</key>
<string></string>
<string>Réponse à %2$@, %3$@ &amp; %1$d autres</string>
</dict>
</dict>
<key>reposts_count</key>
@ -114,6 +122,8 @@
<string>d</string>
<key>one</key>
<string>Republication</string>
<key>many</key>
<string>Republications</string>
<key>other</key>
<string>Republications</string>
</dict>
@ -130,6 +140,8 @@
<string>@</string>
<key>one</key>
<string>%2$@ sat</string>
<key>many</key>
<string>%2$@ sats</string>
<key>other</key>
<string>%2$@ sats</string>
</dict>
@ -146,6 +158,8 @@
<string>d</string>
<key>one</key>
<string>Zap</string>
<key>many</key>
<string>Zaps</string>
<key>other</key>
<string>Zaps</string>
</dict>

Binary file not shown.

View File

@ -4,6 +4,8 @@
<dict>
<key>collapsed_event_view_other_notes</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@NOTES@</string>
<key>NOTES</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
@ -11,13 +13,13 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>other</key>
<string>%d Note Lainnya</string>
<string>... %d Note Lainnya ...</string>
</dict>
<key>NSStringLocalizedFormatKey</key>
<string>··· %#@NOTES@ ···</string>
</dict>
<key>followers_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@FOLLOWERS@</string>
<key>FOLLOWERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
@ -27,8 +29,6 @@
<key>other</key>
<string>Pengikut</string>
</dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@FOLLOWERS@</string>
</dict>
<key>reactions_count</key>
<dict>
@ -61,7 +61,7 @@
<key>replying_to_one_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Membalas ke %@%#@OTHERS@</string>
<string>%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
@ -69,15 +69,13 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>other</key>
<string>&amp; %d lainnya</string>
<key>zero</key>
<string></string>
<string>Membalas ke %2$@ &amp; %1$d lainnya</string>
</dict>
</dict>
<key>replying_to_two_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Membalas ke %@, %@%#@OTHERS@</string>
<string>%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
@ -85,9 +83,7 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>other</key>
<string>&amp; %d lainnya</string>
<key>zero</key>
<string></string>
<string>Membalas ke %2$@, %3$@ &amp; %1$d lainnya</string>
</dict>
</dict>
<key>reposts_count</key>

Binary file not shown.

View File

@ -4,6 +4,8 @@
<dict>
<key>collapsed_event_view_other_notes</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@NOTES@</string>
<key>NOTES</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
@ -11,15 +13,17 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%d altra nota</string>
<string>... %d altra nota ...</string>
<key>many</key>
<string>... %d altre note ...</string>
<key>other</key>
<string>%d altre note</string>
<string>... %d altre note ...</string>
</dict>
<key>NSStringLocalizedFormatKey</key>
<string>··· %#@NOTES@ ···</string>
</dict>
<key>followers_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@FOLLOWERS@</string>
<key>FOLLOWERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
@ -28,11 +32,11 @@
<string>d</string>
<key>one</key>
<string>Seguace</string>
<key>many</key>
<string>Seguaci</string>
<key>other</key>
<string>Seguaci</string>
</dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@FOLLOWERS@</string>
</dict>
<key>reactions_count</key>
<dict>
@ -46,6 +50,8 @@
<string>d</string>
<key>one</key>
<string>Reazione</string>
<key>many</key>
<string>Reazioni</string>
<key>other</key>
<string>Reazioni</string>
</dict>
@ -62,6 +68,8 @@
<string>d</string>
<key>one</key>
<string>Relè</string>
<key>many</key>
<string>Relè</string>
<key>other</key>
<string>Relè</string>
</dict>
@ -69,7 +77,7 @@
<key>replying_to_one_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Rispondendo a %@%#@OTHERS@</string>
<string>%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
@ -77,17 +85,17 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string> &amp; %d altro</string>
<string>Rispondendo a %2$@ &amp; %1$d altro</string>
<key>many</key>
<string>Rispondendo a %2$@ &amp; %1$d altri</string>
<key>other</key>
<string> &amp; %d altri</string>
<key>zero</key>
<string></string>
<string>Rispondendo a %2$@ &amp; %1$d altri</string>
</dict>
</dict>
<key>replying_to_two_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Rispondendo a %@, %@%#@OTHERS@</string>
<string>%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
@ -95,11 +103,11 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string> &amp; %d altro</string>
<string>Rispondendo a %2$@, %3$@ &amp; %1$d altro</string>
<key>many</key>
<string>Rispondendo a %2$@, %3$@ &amp; %1$d altri</string>
<key>other</key>
<string> &amp; %d altri</string>
<key>zero</key>
<string></string>
<string>Rispondendo a %2$@, %3$@ &amp; %1$d altri</string>
</dict>
</dict>
<key>reposts_count</key>
@ -114,6 +122,8 @@
<string>d</string>
<key>one</key>
<string>Repost</string>
<key>many</key>
<string>I Repost</string>
<key>other</key>
<string>I Repost</string>
</dict>
@ -130,6 +140,8 @@
<string>@</string>
<key>one</key>
<string>%2$@ sat</string>
<key>many</key>
<string>%2$@ sats</string>
<key>other</key>
<string>%2$@ sats</string>
</dict>
@ -146,6 +158,8 @@
<string>d</string>
<key>one</key>
<string>Zap</string>
<key>many</key>
<string>Zaps</string>
<key>other</key>
<string>Zaps</string>
</dict>

Binary file not shown.

Binary file not shown.

View File

@ -4,6 +4,8 @@
<dict>
<key>collapsed_event_view_other_notes</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@NOTES@</string>
<key>NOTES</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
@ -11,13 +13,13 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>other</key>
<string>%d その他のNote</string>
<string>... %d その他のNote ...</string>
</dict>
<key>NSStringLocalizedFormatKey</key>
<string>··· %#@NOTES@ ···</string>
</dict>
<key>followers_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@FOLLOWERS@</string>
<key>FOLLOWERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
@ -27,8 +29,6 @@
<key>other</key>
<string>フォロワー</string>
</dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@FOLLOWERS@</string>
</dict>
<key>reactions_count</key>
<dict>
@ -61,7 +61,7 @@
<key>replying_to_one_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%@%#@OTHERS@ にリプライ</string>
<string>%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
@ -69,15 +69,13 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>other</key>
<string> &amp; %d その他</string>
<key>zero</key>
<string></string>
<string>%2$@ &amp; %1$d その他にリプライ</string>
</dict>
</dict>
<key>replying_to_two_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%@, %@%#@OTHERS@ にリプライ</string>
<string>%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
@ -85,9 +83,7 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>other</key>
<string> &amp; %d その他</string>
<key>zero</key>
<string></string>
<string>%2$@, %3$@ &amp; %1$d その他 にリプライ</string>
</dict>
</dict>
<key>reposts_count</key>

View File

@ -4,164 +4,164 @@
<dict>
<key>collapsed_event_view_other_notes</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@NOTES@</string>
<key>NOTES</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%d cita ziņa</string>
<key>other</key>
<string>%d citas ziņas</string>
<key>zero</key>
<string>%d other notes</string>
<string>... %d other notes ...</string>
<key>one</key>
<string>... %d cita ziņa ...</string>
<key>other</key>
<string>... %d citas ziņas ...</string>
</dict>
<key>NSStringLocalizedFormatKey</key>
<string>··· Ziņas ···</string>
</dict>
<key>followers_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@FOLLOWERS@</string>
<key>FOLLOWERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>zero</key>
<string>Followers</string>
<key>one</key>
<string>Sekotājs</string>
<key>other</key>
<string>Sekotāji</string>
<key>zero</key>
<string>Followers</string>
</dict>
<key>NSStringLocalizedFormatKey</key>
<string>Sekotāji</string>
</dict>
<key>reactions_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Reakcijas</string>
<string>%#@REACTIONS@</string>
<key>REACTIONS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>zero</key>
<string>Reactions</string>
<key>one</key>
<string>Reakcija</string>
<key>other</key>
<string>Reakcijas</string>
<key>zero</key>
<string>Reactions</string>
</dict>
</dict>
<key>relays_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Releji</string>
<string>%#@RELAYS@</string>
<key>RELAYS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>zero</key>
<string>Relays</string>
<key>one</key>
<string>Relejs</string>
<key>other</key>
<string>Releji</string>
<key>zero</key>
<string>Relays</string>
</dict>
</dict>
<key>replying_to_one_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Atbildot %@% #Citam</string>
<string>%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string> &amp; %d cits</string>
<key>other</key>
<string> &amp; %d citiem</string>
<key>zero</key>
<string></string>
<string>Atbildot %2$@ &amp; %1$d others</string>
<key>one</key>
<string>Atbildot %2$@ &amp; %1$d cits</string>
<key>other</key>
<string>Atbildot %2$@ &amp; %1$d citiem</string>
</dict>
</dict>
<key>replying_to_two_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Atbildot %@, %@%#Citiem</string>
<string>%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string> &amp; %d cits</string>
<key>other</key>
<string> &amp; %d citiem</string>
<key>zero</key>
<string></string>
<string>Atbildot %2$@, %3$@ &amp; %1$d others</string>
<key>one</key>
<string>Atbildot %2$@, %3$@ &amp; %1$d cits</string>
<key>other</key>
<string>Atbildot %2$@, %3$@ &amp; %1$d citiem</string>
</dict>
</dict>
<key>reposts_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Pārpublicējumi</string>
<string>%#@REPOSTS@</string>
<key>REPOSTS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>zero</key>
<string>Reposts</string>
<key>one</key>
<string>Pārpublicēt</string>
<key>other</key>
<string>Pārpublicējumi</string>
<key>zero</key>
<string>Reposts</string>
</dict>
</dict>
<key>sats_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%1$#@Sats@</string>
<string>%1$#@SATS@</string>
<key>SATS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>@</string>
<key>zero</key>
<string>%2$@ sats</string>
<key>one</key>
<string>%2$@ sati</string>
<key>other</key>
<string>%2$@ sati</string>
<key>zero</key>
<string>%2$@ sats</string>
</dict>
</dict>
<key>zaps_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Zapi</string>
<string>%#@ZAPS@</string>
<key>ZAPS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>zero</key>
<string>Zaps</string>
<key>one</key>
<string>Zaps</string>
<key>other</key>
<string>Zapi</string>
<key>zero</key>
<string>Zaps</string>
</dict>
</dict>
</dict>

Binary file not shown.

Binary file not shown.

View File

@ -4,6 +4,8 @@
<dict>
<key>collapsed_event_view_other_notes</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@NOTES@</string>
<key>NOTES</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
@ -11,15 +13,15 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%d andere notitie</string>
<string>... %d andere notitie ...</string>
<key>other</key>
<string>%d andere notities</string>
<string>... %d andere notities ...</string>
</dict>
<key>NSStringLocalizedFormatKey</key>
<string>··· %#@NOTES@ ···</string>
</dict>
<key>followers_count</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@FOLLOWERS@</string>
<key>FOLLOWERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
@ -31,8 +33,6 @@
<key>other</key>
<string>Volgers</string>
</dict>
<key>NSStringLocalizedFormatKey</key>
<string>%#@FOLLOWERS@</string>
</dict>
<key>reactions_count</key>
<dict>
@ -69,7 +69,7 @@
<key>replying_to_one_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Antwoord aan %@%#@OTHERS@</string>
<string>%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
@ -77,17 +77,15 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>en %d andere gebruiker</string>
<string>Antwoord aan %2$@ en %1$d andere gebruiker</string>
<key>other</key>
<string>en %d andere gebruikers</string>
<key>zero</key>
<string></string>
<string>Antwoord aan %2$@ en %1$d andere gebruikers</string>
</dict>
</dict>
<key>replying_to_two_and_others</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Antwoord aan %@ en %@%#@OTHERS@</string>
<string>%#@OTHERS@</string>
<key>OTHERS</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
@ -95,11 +93,9 @@
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>en %d andere gebruiker</string>
<string>Antwoord aan %2$@, %3$@ en %1$d andere gebruiker</string>
<key>other</key>
<string>en %d andere gebruikers</string>
<key>zero</key>
<string></string>
<string>Antwoord aan %2$@, %3$@ en %1$d andere gebruikers</string>
</dict>
</dict>
<key>reposts_count</key>

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More