Compare commits
52 Commits
translatio
...
tyiu/notif
| Author | SHA1 | Date | |
|---|---|---|---|
|
3383b7b7a6
|
|||
|
|
5a238502cb | ||
|
|
b0aac1fc42 | ||
|
|
72b51a81de | ||
|
|
8ec1fa29b1 | ||
|
|
81683f980a | ||
|
|
b9fc3f90d1 | ||
|
|
695699aa10 | ||
|
|
0a4e75bfec | ||
|
|
9fef2f071a | ||
|
|
c03b4cac11 | ||
|
|
b773df1204 | ||
|
|
c7a34379dd | ||
|
|
eabf37e35c | ||
|
|
e11147b217 | ||
|
|
7674f42596 | ||
|
|
8c37c8f008 | ||
|
|
74dbbcf1a2 | ||
|
|
e3283fc8f8 | ||
|
|
54fdcd1c84 | ||
|
|
5e0ff1a6a0 | ||
| 6517dcba3f | |||
|
|
63e28d4d79 | ||
| e5c0400b54 | |||
|
|
c6c47e824a | ||
|
866e93d338
|
|||
|
f75fc7eebe
|
|||
|
|
d19596c17e | ||
|
|
0b40cd127c | ||
|
|
754ee254e9 | ||
|
|
963cb37762 | ||
|
|
00da97307e | ||
|
|
312c798bb5 | ||
|
|
7110650267 | ||
|
|
242c1011d9 | ||
|
|
e203eece85 | ||
|
|
1b60524070 | ||
|
|
d15a2f0401 | ||
|
|
159d0fa2b5 | ||
|
|
61fddf800e | ||
|
|
b6d5b6f45e | ||
|
|
f5ed9cd5d4 | ||
|
|
57006b928b | ||
|
fd596241a2
|
|||
|
|
b2ee924692 | ||
|
|
6fc70748fe | ||
|
|
5e972dbf2d | ||
|
|
4ebdd01b6c | ||
|
|
13c0c0d679 | ||
|
|
8297859f18 | ||
|
|
e996d5703b | ||
|
|
dfc397337b |
16
CHANGELOG.md
16
CHANGELOG.md
@@ -1,3 +1,18 @@
|
||||
## [1.4.0] - 2023-03-27
|
||||
|
||||
### Added
|
||||
|
||||
- Local zap notifications (Swift)
|
||||
- Add support for video uploads (Swift)
|
||||
- Auto Translation (Terry Yiu)
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed small notification hit boxes (Terry Yiu)
|
||||
|
||||
[1.4.0]: https://github.com/damus-io/damus/releases/tag/v1.4.0
|
||||
|
||||
## [1.3.0-7] - 2023-03-24
|
||||
|
||||
- New experimental timeline view
|
||||
@@ -806,3 +821,4 @@
|
||||
|
||||
[0.1.2]: https://github.com/damus-io/damus/releases/tag/v0.1.2
|
||||
|
||||
|
||||
|
||||
@@ -129,6 +129,7 @@
|
||||
4C75EFBB2804A34C0006080F /* ProofOfWork.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFBA2804A34C0006080F /* ProofOfWork.swift */; };
|
||||
4C7FF7D52823313F009601DB /* Mentions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7FF7D42823313F009601DB /* Mentions.swift */; };
|
||||
4C8682872814DE470026224F /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8682862814DE470026224F /* ProfileView.swift */; };
|
||||
4C8EC52529D1FA6C0085D9A8 /* DamusColors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8EC52429D1FA6C0085D9A8 /* DamusColors.swift */; };
|
||||
4C90BD162839DB54008EE7EF /* NostrMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD152839DB54008EE7EF /* NostrMetadata.swift */; };
|
||||
4C90BD18283A9EE5008EE7EF /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD17283A9EE5008EE7EF /* LoginView.swift */; };
|
||||
4C90BD1A283AA67F008EE7EF /* Bech32.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD19283AA67F008EE7EF /* Bech32.swift */; };
|
||||
@@ -180,6 +181,7 @@
|
||||
4CE0E2AF29A2E82100DB4CA2 /* EventHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE0E2AE29A2E82100DB4CA2 /* EventHolder.swift */; };
|
||||
4CE0E2B229A3DF6900DB4CA2 /* LoadMoreButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE0E2B129A3DF6900DB4CA2 /* LoadMoreButton.swift */; };
|
||||
4CE0E2B629A3ED5500DB4CA2 /* InnerTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE0E2B529A3ED5500DB4CA2 /* InnerTimelineView.swift */; };
|
||||
4CE4F0F229D4FCFA005914DB /* DebouncedOnChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F0F129D4FCFA005914DB /* DebouncedOnChange.swift */; };
|
||||
4CE4F8CD281352B30009DFBB /* Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F8CC281352B30009DFBB /* Notifications.swift */; };
|
||||
4CE4F9DE2852768D00C00DD9 /* ConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F9DD2852768D00C00DD9 /* ConfigView.swift */; };
|
||||
4CE4F9E1285287B800C00DD9 /* TextFieldAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F9E0285287B800C00DD9 /* TextFieldAlert.swift */; };
|
||||
@@ -232,7 +234,7 @@
|
||||
5C513FBA297F72980072348F /* CustomPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C513FB9297F72980072348F /* CustomPicker.swift */; };
|
||||
5C513FCC2984ACA60072348F /* QRCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C513FCB2984ACA60072348F /* QRCodeView.swift */; };
|
||||
5CF72FC229B9142F00124A13 /* ShareAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF72FC129B9142F00124A13 /* ShareAction.swift */; };
|
||||
6439E014296790CF0020672B /* ProfileZoomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6439E013296790CF0020672B /* ProfileZoomView.swift */; };
|
||||
6439E014296790CF0020672B /* ProfilePicImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6439E013296790CF0020672B /* ProfilePicImageView.swift */; };
|
||||
643EA5C8296B764E005081BB /* RelayFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 643EA5C7296B764E005081BB /* RelayFilterView.swift */; };
|
||||
647D9A8D2968520300A295DE /* SideMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647D9A8C2968520300A295DE /* SideMenuView.swift */; };
|
||||
64FBD06F296255C400D9D3B2 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64FBD06E296255C400D9D3B2 /* Theme.swift */; };
|
||||
@@ -299,6 +301,12 @@
|
||||
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>"; };
|
||||
3A325AC429C9E0B8002BE7ED /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
3A325AC529C9E0B8002BE7ED /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
3A325AC629C9E0B8002BE7ED /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = vi; path = vi.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
3A325AC729C9E0CF002BE7ED /* es-ES */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-ES"; path = "es-ES.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
3A325AC829C9E0CF002BE7ED /* es-ES */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-ES"; path = "es-ES.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
||||
3A325AC929C9E0CF002BE7ED /* es-ES */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "es-ES"; path = "es-ES.lproj/Localizable.stringsdict"; 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>"; };
|
||||
@@ -346,6 +354,9 @@
|
||||
3AC524EE298C000B00693EBF /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
3AC524EF298C000B00693EBF /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
3AC524F0298C000B00693EBF /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ar; path = ar.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
3AC59CA729CDDB78007E04A6 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
3AC59CA829CDDB78007E04A6 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
||||
3AC59CA929CDDB78007E04A6 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "pt-BR"; path = "pt-BR.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
||||
3ACB685B297633BC00C46468 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-419"; path = "es-419.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
||||
3ACB685E297633BC00C46468 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-419"; path = "es-419.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
3ACBCB77295FE5C70037388A /* TimeAgoTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimeAgoTests.swift; sourceTree = "<group>"; };
|
||||
@@ -504,6 +515,7 @@
|
||||
4C75EFBA2804A34C0006080F /* ProofOfWork.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProofOfWork.swift; sourceTree = "<group>"; };
|
||||
4C7FF7D42823313F009601DB /* Mentions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mentions.swift; sourceTree = "<group>"; };
|
||||
4C8682862814DE470026224F /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = "<group>"; };
|
||||
4C8EC52429D1FA6C0085D9A8 /* DamusColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusColors.swift; sourceTree = "<group>"; };
|
||||
4C90BD152839DB54008EE7EF /* NostrMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrMetadata.swift; sourceTree = "<group>"; };
|
||||
4C90BD17283A9EE5008EE7EF /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = "<group>"; };
|
||||
4C90BD19283AA67F008EE7EF /* Bech32.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bech32.swift; sourceTree = "<group>"; };
|
||||
@@ -555,6 +567,7 @@
|
||||
4CE0E2AE29A2E82100DB4CA2 /* EventHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventHolder.swift; sourceTree = "<group>"; };
|
||||
4CE0E2B129A3DF6900DB4CA2 /* LoadMoreButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadMoreButton.swift; sourceTree = "<group>"; };
|
||||
4CE0E2B529A3ED5500DB4CA2 /* InnerTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InnerTimelineView.swift; sourceTree = "<group>"; };
|
||||
4CE4F0F129D4FCFA005914DB /* DebouncedOnChange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebouncedOnChange.swift; sourceTree = "<group>"; };
|
||||
4CE4F8CC281352B30009DFBB /* Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifications.swift; sourceTree = "<group>"; };
|
||||
4CE4F9DD2852768D00C00DD9 /* ConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigView.swift; sourceTree = "<group>"; };
|
||||
4CE4F9E0285287B800C00DD9 /* TextFieldAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldAlert.swift; sourceTree = "<group>"; };
|
||||
@@ -610,7 +623,7 @@
|
||||
5C513FB9297F72980072348F /* CustomPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPicker.swift; sourceTree = "<group>"; };
|
||||
5C513FCB2984ACA60072348F /* QRCodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeView.swift; sourceTree = "<group>"; };
|
||||
5CF72FC129B9142F00124A13 /* ShareAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareAction.swift; sourceTree = "<group>"; };
|
||||
6439E013296790CF0020672B /* ProfileZoomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileZoomView.swift; sourceTree = "<group>"; };
|
||||
6439E013296790CF0020672B /* ProfilePicImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePicImageView.swift; sourceTree = "<group>"; };
|
||||
643EA5C7296B764E005081BB /* RelayFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayFilterView.swift; sourceTree = "<group>"; };
|
||||
647D9A8C2968520300A295DE /* SideMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideMenuView.swift; sourceTree = "<group>"; };
|
||||
64FBD06E296255C400D9D3B2 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = "<group>"; };
|
||||
@@ -862,7 +875,6 @@
|
||||
647D9A8C2968520300A295DE /* SideMenuView.swift */,
|
||||
9609F057296E220800069BF3 /* BannerImageView.swift */,
|
||||
4CB8838E296F781C00DC99E7 /* ReactionsView.swift */,
|
||||
6439E013296790CF0020672B /* ProfileZoomView.swift */,
|
||||
4CF0ABD529817F5B00D66079 /* ReportView.swift */,
|
||||
4CF0ABE42981EE0C00D66079 /* EULAView.swift */,
|
||||
3AA247FE297E3D900090C62D /* RepostsView.swift */,
|
||||
@@ -927,6 +939,7 @@
|
||||
3A3040F029A8FF97008A0F29 /* LocalizationUtil.swift */,
|
||||
4C30AC7729A577AB00E2BD5A /* EventCache.swift */,
|
||||
4C9BB83029C0ED4F00FC4E37 /* DisplayName.swift */,
|
||||
4CE4F0F129D4FCFA005914DB /* DebouncedOnChange.swift */,
|
||||
);
|
||||
path = Util;
|
||||
sourceTree = "<group>";
|
||||
@@ -1042,6 +1055,7 @@
|
||||
4CB883AF297705DD00DC99E7 /* ZapButton.swift */,
|
||||
4C42812B298C848200DBF26F /* TranslateView.swift */,
|
||||
7CFF6316299FEFE5005D382A /* SelectableText.swift */,
|
||||
4C8EC52429D1FA6C0085D9A8 /* DamusColors.swift */,
|
||||
);
|
||||
path = Components;
|
||||
sourceTree = "<group>";
|
||||
@@ -1195,6 +1209,7 @@
|
||||
children = (
|
||||
4CFF8F6229CC9AD7008DB934 /* ImageContextMenuModifier.swift */,
|
||||
4CFF8F6629CC9E3A008DB934 /* ImageView.swift */,
|
||||
6439E013296790CF0020672B /* ProfilePicImageView.swift */,
|
||||
4CFF8F6829CC9ED1008DB934 /* ImageContainerView.swift */,
|
||||
);
|
||||
path = Images;
|
||||
@@ -1309,32 +1324,35 @@
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
Base,
|
||||
"es-419",
|
||||
"en-US",
|
||||
"tr-TR",
|
||||
"fr-FR",
|
||||
"lv-LV",
|
||||
"it-IT",
|
||||
de,
|
||||
"pt-PT",
|
||||
"pl-PL",
|
||||
ar,
|
||||
nl,
|
||||
"zh-CN",
|
||||
"el-GR",
|
||||
ja,
|
||||
id,
|
||||
bg,
|
||||
cs,
|
||||
de,
|
||||
"el-GR",
|
||||
"en-US",
|
||||
"es-419",
|
||||
"es-ES",
|
||||
fa,
|
||||
"fr-CA",
|
||||
"fr-FR",
|
||||
"hu-HU",
|
||||
id,
|
||||
"it-IT",
|
||||
ja,
|
||||
ko,
|
||||
"lv-LV",
|
||||
nl,
|
||||
"pl-PL",
|
||||
"pt-BR",
|
||||
"pt-PT",
|
||||
ru,
|
||||
"sv-SE",
|
||||
"tr-TR",
|
||||
uk,
|
||||
vi,
|
||||
"zh-CN",
|
||||
"zh-HK",
|
||||
"zh-TW",
|
||||
uk,
|
||||
bg,
|
||||
fa,
|
||||
ko,
|
||||
"hu-HU",
|
||||
"sv-SE",
|
||||
"fr-CA",
|
||||
);
|
||||
mainGroup = 4CE6DEDA27F7A08100C66700;
|
||||
packageReferences = (
|
||||
@@ -1544,9 +1562,10 @@
|
||||
4CB8838629656C8B00DC99E7 /* NIP05.swift in Sources */,
|
||||
4CF0ABD82981980C00D66079 /* Lists.swift in Sources */,
|
||||
4C30AC8029A6A53F00E2BD5A /* ProfilePicturesView.swift in Sources */,
|
||||
4C8EC52529D1FA6C0085D9A8 /* DamusColors.swift in Sources */,
|
||||
4C5C7E6A284EDE2E00A22DF5 /* SearchResultsView.swift in Sources */,
|
||||
7C60CAEF298471A1009C80D6 /* CoreSVG.swift in Sources */,
|
||||
6439E014296790CF0020672B /* ProfileZoomView.swift in Sources */,
|
||||
6439E014296790CF0020672B /* ProfilePicImageView.swift in Sources */,
|
||||
4CE6DF1627F8DEBF00C66700 /* RelayConnection.swift in Sources */,
|
||||
4C3BEFD6281D995700B3DE84 /* ActionBarModel.swift in Sources */,
|
||||
4C363AA428296DEE006E126D /* SearchModel.swift in Sources */,
|
||||
@@ -1577,6 +1596,7 @@
|
||||
4CFF8F6929CC9ED1008DB934 /* ImageContainerView.swift in Sources */,
|
||||
4C54AA0729A540BA003E4487 /* NotificationsModel.swift in Sources */,
|
||||
4CB55EF3295E5D59007FD187 /* RecommendedRelayView.swift in Sources */,
|
||||
4CE4F0F229D4FCFA005914DB /* DebouncedOnChange.swift in Sources */,
|
||||
4CF0ABEC29844B4700D66079 /* AnyDecodable.swift in Sources */,
|
||||
4C5F9118283D88E40052CD1C /* FollowingModel.swift in Sources */,
|
||||
643EA5C8296B764E005081BB /* RelayFilterView.swift in Sources */,
|
||||
@@ -1690,6 +1710,9 @@
|
||||
3AD14EB529C40F38009D2D9C /* hu-HU */,
|
||||
3AD14EB829C40F3F009D2D9C /* sv-SE */,
|
||||
3AD14EBC29C40F47009D2D9C /* fr-CA */,
|
||||
3A325AC629C9E0B8002BE7ED /* vi */,
|
||||
3A325AC929C9E0CF002BE7ED /* es-ES */,
|
||||
3AC59CA929CDDB78007E04A6 /* pt-BR */,
|
||||
);
|
||||
name = Localizable.stringsdict;
|
||||
sourceTree = "<group>";
|
||||
@@ -1722,6 +1745,9 @@
|
||||
3AD14EB629C40F38009D2D9C /* hu-HU */,
|
||||
3AD14EB929C40F3F009D2D9C /* sv-SE */,
|
||||
3AD14EBB29C40F47009D2D9C /* fr-CA */,
|
||||
3A325AC529C9E0B8002BE7ED /* vi */,
|
||||
3A325AC829C9E0CF002BE7ED /* es-ES */,
|
||||
3AC59CA829CDDB78007E04A6 /* pt-BR */,
|
||||
);
|
||||
name = InfoPlist.strings;
|
||||
sourceTree = "<group>";
|
||||
@@ -1755,6 +1781,9 @@
|
||||
3AD14EB729C40F38009D2D9C /* hu-HU */,
|
||||
3AD14EBA29C40F3F009D2D9C /* sv-SE */,
|
||||
3AD14EBD29C40F47009D2D9C /* fr-CA */,
|
||||
3A325AC429C9E0B8002BE7ED /* vi */,
|
||||
3A325AC729C9E0CF002BE7ED /* es-ES */,
|
||||
3AC59CA729CDDB78007E04A6 /* pt-BR */,
|
||||
);
|
||||
name = Localizable.strings;
|
||||
sourceTree = "<group>";
|
||||
@@ -1890,7 +1919,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 7;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -1913,7 +1942,7 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)",
|
||||
);
|
||||
MARKETING_VERSION = 1.3.0;
|
||||
MARKETING_VERSION = 1.4.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.jb55.damus2;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
@@ -1932,7 +1961,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 7;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -1955,7 +1984,7 @@
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)",
|
||||
);
|
||||
MARKETING_VERSION = 1.3.0;
|
||||
MARKETING_VERSION = 1.4.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.jb55.damus2;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xF4",
|
||||
"green" : "0xEE",
|
||||
"red" : "0xEE"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x1E",
|
||||
"green" : "0x1C",
|
||||
"red" : "0x1C"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
21
damus/Assets.xcassets/bitcoin-logo.imageset/Contents.json
vendored
Normal file
21
damus/Assets.xcassets/bitcoin-logo.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "bitcoin-logo.svg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
7
damus/Assets.xcassets/bitcoin-logo.imageset/bitcoin-logo.svg
vendored
Normal file
7
damus/Assets.xcassets/bitcoin-logo.imageset/bitcoin-logo.svg
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20px" height="20px" viewBox="0 0 20 20" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(96.862745%,57.647059%,10.196078%);fill-opacity:1;" d="M 19.699219 12.417969 C 18.363281 17.777344 12.9375 21.035156 7.582031 19.699219 C 2.226562 18.363281 -1.035156 12.9375 0.300781 7.582031 C 1.636719 2.222656 7.0625 -1.035156 12.417969 0.300781 C 17.773438 1.632812 21.035156 7.0625 19.699219 12.417969 Z M 19.699219 12.417969 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(100%,100%,100%);fill-opacity:1;" d="M 14.410156 8.574219 C 14.609375 7.246094 13.59375 6.53125 12.210938 6.050781 L 12.660156 4.25 L 11.5625 3.976562 L 11.125 5.730469 C 10.835938 5.660156 10.539062 5.589844 10.246094 5.523438 L 10.6875 3.757812 L 9.589844 3.484375 L 9.140625 5.285156 C 8.902344 5.230469 8.667969 5.179688 8.4375 5.121094 L 8.441406 5.117188 L 6.925781 4.738281 L 6.636719 5.910156 C 6.636719 5.910156 7.449219 6.097656 7.433594 6.109375 C 7.875 6.21875 7.957031 6.511719 7.941406 6.746094 L 7.429688 8.800781 C 7.460938 8.808594 7.5 8.820312 7.546875 8.835938 L 7.429688 8.808594 L 6.710938 11.683594 C 6.65625 11.820312 6.519531 12.023438 6.210938 11.945312 C 6.21875 11.960938 5.410156 11.746094 5.410156 11.746094 L 4.867188 13 L 6.296875 13.359375 C 6.5625 13.425781 6.820312 13.492188 7.078125 13.558594 L 6.621094 15.382812 L 7.71875 15.65625 L 8.167969 13.851562 C 8.46875 13.933594 8.757812 14.007812 9.042969 14.078125 L 8.59375 15.875 L 9.691406 16.148438 L 10.144531 14.328125 C 12.015625 14.683594 13.425781 14.539062 14.015625 12.847656 C 14.492188 11.484375 13.992188 10.699219 13.007812 10.1875 C 13.726562 10.019531 14.265625 9.550781 14.410156 8.574219 Z M 11.902344 12.089844 C 11.5625 13.453125 9.269531 12.71875 8.523438 12.53125 L 9.128906 10.117188 C 9.871094 10.300781 12.253906 10.667969 11.902344 12.089844 Z M 12.242188 8.554688 C 11.933594 9.796875 10.023438 9.164062 9.402344 9.011719 L 9.949219 6.820312 C 10.570312 6.976562 12.5625 7.261719 12.242188 8.554688 Z M 12.242188 8.554688 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -8,8 +8,8 @@
|
||||
import SwiftUI
|
||||
|
||||
let RECTANGLE_GRADIENT = LinearGradient(gradient: Gradient(colors: [
|
||||
Color("DamusPurple"),
|
||||
Color("DamusBlue")
|
||||
DamusColors.purple,
|
||||
DamusColors.blue
|
||||
]), startPoint: .leading, endPoint: .trailing)
|
||||
|
||||
struct CustomPicker<SelectionValue: Hashable, Content: View>: View {
|
||||
@@ -56,6 +56,6 @@ struct CustomPicker<SelectionValue: Hashable, Content: View>: View {
|
||||
}
|
||||
|
||||
func textColor() -> Color {
|
||||
colorScheme == .light ? Color("DamusBlack") : Color("DamusWhite")
|
||||
colorScheme == .light ? DamusColors.black : DamusColors.white
|
||||
}
|
||||
}
|
||||
|
||||
22
damus/Components/DamusColors.swift
Normal file
22
damus/Components/DamusColors.swift
Normal file
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// DamusColors.swift
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2023-03-27.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
class DamusColors {
|
||||
static let adaptableGrey = Color("DamusAdaptableGrey")
|
||||
static let white = Color("DamusWhite")
|
||||
static let black = Color("DamusBlack")
|
||||
static let lightGrey = Color("DamusLightGrey")
|
||||
static let mediumGrey = Color("DamusMediumGrey")
|
||||
static let darkGrey = Color("DamusDarkGrey")
|
||||
static let green = Color("DamusGreen")
|
||||
static let purple = Color("DamusPurple")
|
||||
static let blue = Color("DamusBlue")
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ struct InvoiceView: View {
|
||||
.foregroundColor(.gray)
|
||||
} else {
|
||||
Image(systemName: "checkmark.circle")
|
||||
.foregroundColor(Color("DamusGreen"))
|
||||
.foregroundColor(DamusColors.green)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,19 +24,33 @@ struct NIP05Badge: View {
|
||||
self.clickable = clickable
|
||||
}
|
||||
|
||||
var nip05_color: Color {
|
||||
return get_nip05_color(pubkey: pubkey, contacts: contacts)
|
||||
var nip05_color: Bool {
|
||||
return use_nip05_color(pubkey: pubkey, contacts: contacts)
|
||||
}
|
||||
|
||||
var Seal: some View {
|
||||
Group {
|
||||
if nip05_color {
|
||||
LINEAR_GRADIENT
|
||||
.mask(Image(systemName: "checkmark.seal.fill")
|
||||
.resizable()
|
||||
).frame(width: 14, height: 14)
|
||||
} else {
|
||||
Image(systemName: "checkmark.seal.fill")
|
||||
.font(.footnote)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 2) {
|
||||
Image(systemName: "checkmark.seal.fill")
|
||||
.font(.footnote)
|
||||
.foregroundColor(nip05_color)
|
||||
Seal
|
||||
|
||||
if show_domain {
|
||||
if clickable {
|
||||
Text(nip05.host)
|
||||
.foregroundColor(nip05_color)
|
||||
.nip05_colorized(gradient: nip05_color)
|
||||
.onTapGesture {
|
||||
if let nip5url = nip05.siteUrl {
|
||||
openURL(nip5url)
|
||||
@@ -44,7 +58,7 @@ struct NIP05Badge: View {
|
||||
}
|
||||
} else {
|
||||
Text(nip05.host)
|
||||
.foregroundColor(nip05_color)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,8 +66,19 @@ struct NIP05Badge: View {
|
||||
}
|
||||
}
|
||||
|
||||
func get_nip05_color(pubkey: String, contacts: Contacts) -> Color {
|
||||
return contacts.is_friend_or_self(pubkey) ? .accentColor : .gray
|
||||
extension View {
|
||||
func nip05_colorized(gradient: Bool) -> some View {
|
||||
if gradient {
|
||||
return AnyView(self.foregroundStyle(LINEAR_GRADIENT))
|
||||
} else {
|
||||
return AnyView(self.foregroundColor(.gray))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func use_nip05_color(pubkey: String, contacts: Contacts) -> Bool {
|
||||
return contacts.is_friend_or_self(pubkey) ? true : false
|
||||
}
|
||||
|
||||
struct NIP05Badge_Previews: PreviewProvider {
|
||||
|
||||
@@ -18,6 +18,8 @@ struct TranslateView: View {
|
||||
@State var translated_note: String? = nil
|
||||
@State var show_translated_note: Bool = false
|
||||
@State var translated_artifacts: NoteArtifacts? = nil
|
||||
|
||||
let preferredLanguages = Set(Locale.preferredLanguages.map { localeToLanguage($0) })
|
||||
|
||||
var TranslateButton: some View {
|
||||
Button(NSLocalizedString("Translate Note", comment: "Button to translate note from different language.")) {
|
||||
@@ -28,7 +30,7 @@ struct TranslateView: View {
|
||||
|
||||
func Translated(lang: String, artifacts: NoteArtifacts) -> some View {
|
||||
return Group {
|
||||
Button(NSLocalizedString("Translated from \(lang)", comment: "Button to indicate that the note has been translated from a different language.")) {
|
||||
Button(String(format: NSLocalizedString("Translated from %@", comment: "Button to indicate that the note has been translated from a different language."), lang)) {
|
||||
show_translated_note = false
|
||||
}
|
||||
.translate_button_style()
|
||||
@@ -38,7 +40,7 @@ struct TranslateView: View {
|
||||
}
|
||||
|
||||
func CheckingStatus(lang: String) -> some View {
|
||||
return Button(NSLocalizedString("Translating from \(lang)...", comment: "Button to indicate that the note is in the process of being translated from a different language.")) {
|
||||
return Button(String(format: NSLocalizedString("Translating from %@...", comment: "Button to indicate that the note is in the process of being translated from a different language."), lang)) {
|
||||
show_translated_note = false
|
||||
}
|
||||
.translate_button_style()
|
||||
@@ -80,24 +82,7 @@ struct TranslateView: View {
|
||||
currentLanguage = Locale.current.languageCode ?? "en"
|
||||
}
|
||||
|
||||
// Rely on Apple's NLLanguageRecognizer to tell us which language it thinks the note is in
|
||||
// and filter on only the text portions of the content as URLs and hashtags confuse the language recognizer.
|
||||
let originalBlocks = event.blocks(damus_state.keypair.privkey)
|
||||
let originalOnlyText = originalBlocks.compactMap { $0.is_text }.joined(separator: " ")
|
||||
|
||||
// Only accept language recognition hypothesis if there's at least a 50% probability that it's accurate.
|
||||
let languageRecognizer = NLLanguageRecognizer()
|
||||
languageRecognizer.processString(originalOnlyText)
|
||||
noteLanguage = languageRecognizer.languageHypotheses(withMaximum: 1).first(where: { $0.value >= 0.5 })?.key.rawValue ?? currentLanguage
|
||||
|
||||
if let lang = noteLanguage, noteLanguage != currentLanguage {
|
||||
// If the detected dominant language is a variant, remove the variant component and just take the language part as translation services typically only supports the variant-less language.
|
||||
if #available(iOS 16, *) {
|
||||
noteLanguage = Locale.LanguageCode(stringLiteral: lang).identifier(.alpha2)
|
||||
} else {
|
||||
noteLanguage = NSLocale(localeIdentifier: lang).languageCode
|
||||
}
|
||||
}
|
||||
noteLanguage = event.note_language(damus_state.keypair.privkey) ?? currentLanguage
|
||||
|
||||
guard let note_lang = noteLanguage else {
|
||||
noteLanguage = currentLanguage
|
||||
@@ -106,9 +91,9 @@ struct TranslateView: View {
|
||||
return
|
||||
}
|
||||
|
||||
if note_lang != currentLanguage {
|
||||
if !preferredLanguages.contains(note_lang) {
|
||||
do {
|
||||
// If the note language is different from our language, send a translation request.
|
||||
// If the note language is different from our preferred languages, send a translation request.
|
||||
let translator = Translator(damus_state.settings)
|
||||
let originalContent = event.get_content(damus_state.keypair.privkey)
|
||||
translated_note = try await translator.translate(originalContent, from: note_lang, to: currentLanguage)
|
||||
@@ -132,11 +117,21 @@ struct TranslateView: View {
|
||||
}
|
||||
|
||||
checkingTranslationStatus = false
|
||||
|
||||
|
||||
show_translated_note = damus_state.settings.auto_translate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension View {
|
||||
func translate_button_style() -> some View {
|
||||
return self
|
||||
.font(.footnote)
|
||||
.contentShape(Rectangle())
|
||||
.padding([.top, .bottom], 10)
|
||||
}
|
||||
}
|
||||
|
||||
struct TranslateView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let ds = test_damus_state()
|
||||
|
||||
@@ -22,6 +22,7 @@ struct WebsiteLink: View {
|
||||
}, label: {
|
||||
Text(link_text)
|
||||
.font(.footnote)
|
||||
.foregroundStyle(LINEAR_GRADIENT)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,7 +211,7 @@ struct ContentView: View {
|
||||
Image("damus-home")
|
||||
.resizable()
|
||||
.frame(width:30,height:30)
|
||||
.shadow(color: Color("DamusPurple"), radius: 2)
|
||||
.shadow(color: DamusColors.purple, radius: 2)
|
||||
.opacity(isSideBarOpened ? 0 : 1)
|
||||
.animation(isSideBarOpened ? .none : .default, value: isSideBarOpened)
|
||||
} else {
|
||||
@@ -776,7 +776,6 @@ func setup_notifications() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func find_event(state: DamusState, evid: String, search_type: SearchType, find_from: [String]?, callback: @escaping (NostrEvent?) -> ()) {
|
||||
if let ev = state.events.lookup(evid) {
|
||||
callback(ev)
|
||||
|
||||
@@ -46,5 +46,9 @@
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Damus needs access to your camera if you want to upload photos from it</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>Damus needs access to your microphone if you want to upload recorded videos from it</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -130,7 +130,7 @@ class HomeModel: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
func handle_zap_event_with_zapper(_ ev: NostrEvent, our_keypair: Keypair, zapper: String) {
|
||||
func handle_zap_event_with_zapper(profiles: Profiles, ev: NostrEvent, our_keypair: Keypair, zapper: String) {
|
||||
guard let zap = Zap.from_zap_event(zap_ev: ev, zapper: zapper, our_privkey: our_keypair.privkey) else {
|
||||
return
|
||||
}
|
||||
@@ -145,9 +145,13 @@ class HomeModel: ObservableObject {
|
||||
return
|
||||
}
|
||||
|
||||
if handle_last_event(ev: ev, timeline: .notifications) && damus_state.settings.zap_vibration {
|
||||
// Generate zap vibration
|
||||
zap_vibrate(zap_amount: zap.invoice.amount)
|
||||
if handle_last_event(ev: ev, timeline: .notifications) {
|
||||
if damus_state.settings.zap_vibration {
|
||||
// Generate zap vibration
|
||||
zap_vibrate(zap_amount: zap.invoice.amount)
|
||||
}
|
||||
// Create in-app local notification for zap received.
|
||||
create_in_app_zap_notification(profiles: profiles, zap: zap)
|
||||
}
|
||||
|
||||
return
|
||||
@@ -161,7 +165,7 @@ class HomeModel: ObservableObject {
|
||||
|
||||
let our_keypair = damus_state.keypair
|
||||
if let local_zapper = damus_state.profiles.lookup_zapper(pubkey: ptag) {
|
||||
handle_zap_event_with_zapper(ev, our_keypair: our_keypair, zapper: local_zapper)
|
||||
handle_zap_event_with_zapper(profiles: self.damus_state.profiles, ev: ev, our_keypair: our_keypair, zapper: local_zapper)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -180,7 +184,7 @@ class HomeModel: ObservableObject {
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.damus_state.profiles.zappers[ptag] = zapper
|
||||
self.handle_zap_event_with_zapper(ev, our_keypair: our_keypair, zapper: zapper)
|
||||
self.handle_zap_event_with_zapper(profiles: self.damus_state.profiles, ev: ev, our_keypair: our_keypair, zapper: zapper)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -671,6 +675,7 @@ func process_metadata_event(our_pubkey: String, profiles: Profiles, ev: NostrEve
|
||||
|
||||
DispatchQueue.main.async {
|
||||
profiles.validated[ev.pubkey] = validated
|
||||
profiles.nip05_pubkey[nip05] = ev.pubkey
|
||||
notify(.profile_updated, ProfileUpdate(pubkey: ev.pubkey, profile: profile))
|
||||
}
|
||||
}
|
||||
@@ -928,3 +933,42 @@ func zap_vibrate(zap_amount: Int64) {
|
||||
vibration_generator.impactOccurred()
|
||||
}
|
||||
|
||||
func describe_zap_type(_ zap: Zap) -> String? {
|
||||
if zap.private_request != nil {
|
||||
return "Private"
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func create_in_app_zap_notification(profiles: Profiles, zap: Zap) {
|
||||
let content = UNMutableNotificationContent()
|
||||
let typ = describe_zap_type(zap).map({ "\($0) " }) ?? ""
|
||||
|
||||
content.title = typ + "Zap"
|
||||
let satString = zap.invoice.amount == 1000 ? "sat" : "sats"
|
||||
|
||||
let src = zap.private_request ?? zap.request.ev
|
||||
let anon = event_is_anonymous(ev: src)
|
||||
let pk = anon ? "anon" : src.pubkey
|
||||
let profile = profiles.lookup(id: pk)
|
||||
let sats = format_msats_abbrev(zap.invoice.amount)
|
||||
let name = Profile.displayName(profile: profile, pubkey: pk).display_name
|
||||
let message = src.content.count == 0 ? "" : ": \"\(src.content)\""
|
||||
|
||||
content.body = "You received \(sats) \(satString) from \(name)\(message)"
|
||||
content.sound = UNNotificationSound.default
|
||||
|
||||
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
|
||||
|
||||
let request = UNNotificationRequest(identifier: "myZapNotification", content: content, trigger: trigger)
|
||||
|
||||
UNUserNotificationCenter.current().add(request) { error in
|
||||
if let error = error {
|
||||
print("Error: \(error)")
|
||||
} else {
|
||||
print("Local notification scheduled")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,11 +9,37 @@ import Foundation
|
||||
import UIKit
|
||||
|
||||
|
||||
enum MediaUpload {
|
||||
case image(URL)
|
||||
case video(URL)
|
||||
|
||||
var genericFileName: String {
|
||||
"damus_generic_filename.\(file_extension)"
|
||||
}
|
||||
|
||||
var file_extension: String {
|
||||
switch self {
|
||||
case .image(let url):
|
||||
return url.pathExtension
|
||||
case .video(let url):
|
||||
return url.pathExtension
|
||||
}
|
||||
}
|
||||
|
||||
var is_image: Bool {
|
||||
if case .image = self {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
class ImageUploadModel: NSObject, URLSessionTaskDelegate, ObservableObject {
|
||||
@Published var progress: Double? = nil
|
||||
|
||||
func start(img: UIImage, uploader: ImageUploader) async -> ImageUploadResult {
|
||||
let res = await create_image_upload_request(imageToUpload: img, imageUploader: uploader, progress: self)
|
||||
func start(media: MediaUpload, uploader: MediaUploader) async -> ImageUploadResult {
|
||||
let res = await create_upload_request(mediaToUpload: media, mediaUploader: uploader, progress: self)
|
||||
DispatchQueue.main.async {
|
||||
self.progress = nil
|
||||
}
|
||||
|
||||
@@ -94,6 +94,14 @@ enum Block {
|
||||
return nil
|
||||
}
|
||||
|
||||
var is_note_mention: Bool {
|
||||
guard case .mention(let mention) = self else {
|
||||
return false
|
||||
}
|
||||
|
||||
return mention.type == .event
|
||||
}
|
||||
|
||||
var is_mention: Bool {
|
||||
if case .mention = self {
|
||||
return true
|
||||
|
||||
@@ -50,10 +50,10 @@ func get_default_wallet(_ pubkey: String) -> Wallet {
|
||||
}
|
||||
}
|
||||
|
||||
func get_image_uploader(_ pubkey: String) -> ImageUploader {
|
||||
if let defaultImageUploader = UserDefaults.standard.string(forKey: "default_image_uploader"),
|
||||
let defaultImageUploader = ImageUploader(rawValue: defaultImageUploader) {
|
||||
return defaultImageUploader
|
||||
func get_media_uploader(_ pubkey: String) -> MediaUploader {
|
||||
if let defaultMediaUploader = UserDefaults.standard.string(forKey: "default_media_uploader"),
|
||||
let defaultMediaUploader = MediaUploader(rawValue: defaultMediaUploader) {
|
||||
return defaultMediaUploader
|
||||
} else {
|
||||
return .nostrBuild
|
||||
}
|
||||
@@ -98,9 +98,9 @@ class UserSettingsStore: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
@Published var default_image_uploader: ImageUploader {
|
||||
@Published var default_media_uploader: MediaUploader {
|
||||
didSet {
|
||||
UserDefaults.standard.set(default_image_uploader.rawValue, forKey: "default_image_uploader")
|
||||
UserDefaults.standard.set(default_media_uploader.rawValue, forKey: "default_media_uploader")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,6 +128,18 @@ class UserSettingsStore: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
@Published var auto_translate: Bool {
|
||||
didSet {
|
||||
UserDefaults.standard.set(auto_translate, forKey: "auto_translate")
|
||||
}
|
||||
}
|
||||
|
||||
@Published var show_only_preferred_languages: Bool {
|
||||
didSet {
|
||||
UserDefaults.standard.set(show_only_preferred_languages, forKey: "show_only_preferred_languages")
|
||||
}
|
||||
}
|
||||
|
||||
@Published var translation_service: TranslationService {
|
||||
didSet {
|
||||
UserDefaults.standard.set(translation_service.rawValue, forKey: "translation_service")
|
||||
@@ -205,11 +217,13 @@ class UserSettingsStore: ObservableObject {
|
||||
show_wallet_selector = should_show_wallet_selector(pubkey)
|
||||
always_show_images = UserDefaults.standard.object(forKey: "always_show_images") as? Bool ?? false
|
||||
|
||||
default_image_uploader = get_image_uploader(pubkey)
|
||||
default_media_uploader = get_media_uploader(pubkey)
|
||||
|
||||
left_handed = UserDefaults.standard.object(forKey: "left_handed") as? Bool ?? false
|
||||
zap_vibration = UserDefaults.standard.object(forKey: "zap_vibration") as? Bool ?? false
|
||||
disable_animation = should_disable_image_animation()
|
||||
auto_translate = UserDefaults.standard.object(forKey: "auto_translate") as? Bool ?? true
|
||||
show_only_preferred_languages = UserDefaults.standard.object(forKey: "show_only_preferred_languages") as? Bool ?? false
|
||||
|
||||
// Note from @tyiu:
|
||||
// Default translation service is disabled by default for now until we gain some confidence that it is working well in production.
|
||||
|
||||
@@ -10,6 +10,7 @@ import CommonCrypto
|
||||
import secp256k1
|
||||
import secp256k1_implementation
|
||||
import CryptoKit
|
||||
import NaturalLanguage
|
||||
|
||||
|
||||
|
||||
@@ -259,6 +260,25 @@ class NostrEvent: Codable, Identifiable, CustomStringConvertible, Equatable, Has
|
||||
return event_is_reply(self, privkey: privkey)
|
||||
}
|
||||
|
||||
func note_language(_ privkey: String?) -> String? {
|
||||
// Rely on Apple's NLLanguageRecognizer to tell us which language it thinks the note is in
|
||||
// and filter on only the text portions of the content as URLs and hashtags confuse the language recognizer.
|
||||
let originalBlocks = blocks(privkey)
|
||||
let originalOnlyText = originalBlocks.compactMap { $0.is_text }.joined(separator: " ")
|
||||
|
||||
// Only accept language recognition hypothesis if there's at least a 50% probability that it's accurate.
|
||||
let languageRecognizer = NLLanguageRecognizer()
|
||||
languageRecognizer.processString(originalOnlyText)
|
||||
|
||||
guard let locale = languageRecognizer.languageHypotheses(withMaximum: 1).first(where: { $0.value >= 0.5 })?.key.rawValue else {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove the variant component and just take the language part as translation services typically only supports the variant-less language.
|
||||
// Moreover, speakers of one variant can generally understand other variants.
|
||||
return localeToLanguage(locale)
|
||||
}
|
||||
|
||||
public var referenced_ids: [ReferencedId] {
|
||||
return get_referenced_ids(key: "e")
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import UIKit
|
||||
class Profiles {
|
||||
var profiles: [String: TimestampedProfile] = [:]
|
||||
var validated: [String: NIP05] = [:]
|
||||
var nip05_pubkey: [String: String] = [:]
|
||||
var zappers: [String: String] = [:]
|
||||
|
||||
func is_validated(_ pk: String) -> NIP05? {
|
||||
|
||||
69
damus/Util/DebouncedOnChange.swift
Normal file
69
damus/Util/DebouncedOnChange.swift
Normal file
@@ -0,0 +1,69 @@
|
||||
// https://github.com/Tunous/DebouncedOnChange/blob/5670ea13e8ad33e9cc3197f6d13ce492dc0e46ab/Sources/DebouncedOnChange/DebouncedChangeViewModifier.swift
|
||||
|
||||
import SwiftUI
|
||||
import Foundation
|
||||
|
||||
extension View {
|
||||
|
||||
/// Adds a modifier for this view that fires an action only when a time interval in seconds represented by
|
||||
/// `debounceTime` elapses between value changes.
|
||||
///
|
||||
/// Each time the value changes before `debounceTime` passes, the previous action will be cancelled and the next
|
||||
/// action /// will be scheduled to run after that time passes again. This mean that the action will only execute
|
||||
/// after changes to the value /// stay unmodified for the specified `debounceTime` in seconds.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - value: The value to check against when determining whether to run the closure.
|
||||
/// - debounceTime: The time in seconds to wait after each value change before running `action` closure.
|
||||
/// - action: A closure to run when the value changes.
|
||||
/// - Returns: A view that fires an action after debounced time when the specified value changes.
|
||||
public func onChange<Value>(
|
||||
of value: Value,
|
||||
debounceTime: TimeInterval,
|
||||
perform action: @escaping (_ newValue: Value) -> Void
|
||||
) -> some View where Value: Equatable {
|
||||
self.modifier(DebouncedChangeViewModifier(trigger: value, debounceTime: debounceTime, action: action))
|
||||
}
|
||||
}
|
||||
|
||||
private struct DebouncedChangeViewModifier<Value>: ViewModifier where Value: Equatable {
|
||||
let trigger: Value
|
||||
let debounceTime: TimeInterval
|
||||
let action: (Value) -> Void
|
||||
|
||||
@State private var debouncedTask: Task<Void, Never>?
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content.onChange(of: trigger) { value in
|
||||
debouncedTask?.cancel()
|
||||
debouncedTask = Task.delayed(seconds: debounceTime) { @MainActor in
|
||||
action(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Task {
|
||||
|
||||
/// Asynchronously runs the given `operation` in its own task after the specified number of `seconds`.
|
||||
///
|
||||
/// The operation will be executed after specified number of `seconds` passes. You can cancel the task earlier
|
||||
/// for the operation to be skipped.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - time: Delay time in seconds.
|
||||
/// - operation: The operation to execute.
|
||||
/// - Returns: Handle to the task which can be cancelled.
|
||||
@discardableResult
|
||||
public static func delayed(
|
||||
seconds: TimeInterval,
|
||||
operation: @escaping @Sendable () async -> Void
|
||||
) -> Self where Success == Void, Failure == Never {
|
||||
Self {
|
||||
do {
|
||||
try await Task<Never, Never>.sleep(nanoseconds: UInt64(seconds * 1_000_000_000))
|
||||
await operation()
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,3 +21,14 @@ func localizedStringFormat(key: String, locale: Locale?) -> String {
|
||||
let fallback = bundleForLocale(locale: Locale(identifier: "en-US")).localizedString(forKey: key, value: nil, table: nil)
|
||||
return bundle.localizedString(forKey: key, value: fallback, table: nil)
|
||||
}
|
||||
|
||||
/**
|
||||
Removes the variant part of a locale code so that it contains only the language code.
|
||||
*/
|
||||
func localeToLanguage(_ locale: String) -> String? {
|
||||
if #available(iOS 16, *) {
|
||||
return Locale.LanguageCode(stringLiteral: locale).identifier(.alpha2)
|
||||
} else {
|
||||
return NSLocale(localeIdentifier: locale).languageCode
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,11 +39,20 @@ enum NIP05Validation {
|
||||
case valid
|
||||
}
|
||||
|
||||
func validate_nip05(pubkey: String, nip05_str: String) async -> NIP05? {
|
||||
struct FetchedNIP05 {
|
||||
let response: NIP05Response
|
||||
let nip05: NIP05Response
|
||||
}
|
||||
|
||||
func fetch_nip05_str(nip05_str: String) async -> NIP05Response? {
|
||||
guard let nip05 = NIP05.parse(nip05_str) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return await fetch_nip05(nip05: nip05)
|
||||
}
|
||||
|
||||
func fetch_nip05(nip05: NIP05) async -> NIP05Response? {
|
||||
guard let url = nip05.url else {
|
||||
return nil
|
||||
}
|
||||
@@ -57,6 +66,18 @@ func validate_nip05(pubkey: String, nip05_str: String) async -> NIP05? {
|
||||
return nil
|
||||
}
|
||||
|
||||
return decoded
|
||||
}
|
||||
|
||||
func validate_nip05(pubkey: String, nip05_str: String) async -> NIP05? {
|
||||
guard let nip05 = NIP05.parse(nip05_str) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let decoded = await fetch_nip05(nip05: nip05) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let stored_pk = decoded.names[nip05.username] else {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -77,10 +77,10 @@ struct EventActionBar: View {
|
||||
send_like()
|
||||
}
|
||||
}
|
||||
|
||||
Text(verbatim: "\(bar.likes > 0 ? "\(bar.likes)" : "")")
|
||||
.font(.footnote.weight(.medium))
|
||||
.foregroundColor(bar.liked ? Color.accentColor : Color.gray)
|
||||
|
||||
.nip05_colorized(gradient: bar.liked)
|
||||
}
|
||||
|
||||
if let lnurl = self.lnurl {
|
||||
@@ -188,8 +188,15 @@ struct LikeButton: View {
|
||||
amountOfAngleIncrease = 20.0
|
||||
}
|
||||
}) {
|
||||
Image(liked ? "shaka-full" : "shaka-line")
|
||||
.foregroundColor(liked ? .accentColor : .gray)
|
||||
if liked {
|
||||
LINEAR_GRADIENT
|
||||
.mask(Image("shaka-full")
|
||||
.resizable()
|
||||
).frame(width: 14, height: 14)
|
||||
} else {
|
||||
Image("shaka-line")
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
.accessibilityLabel(NSLocalizedString("Like", comment: "Accessibility Label for Like button"))
|
||||
.rotationEffect(Angle(degrees: shouldAnimate ? rotationAngle : 0))
|
||||
|
||||
@@ -29,7 +29,7 @@ struct ShareAction: View {
|
||||
|
||||
var body: some View {
|
||||
|
||||
let col = colorScheme == .light ? Color("DamusMediumGrey") : Color("DamusWhite")
|
||||
let col = colorScheme == .light ? DamusColors.mediumGrey : DamusColors.white
|
||||
|
||||
VStack {
|
||||
Text("Share Note", comment: "Title text to indicate that the buttons below are meant to be used to share a note with others.")
|
||||
@@ -46,9 +46,9 @@ struct ShareAction: View {
|
||||
}
|
||||
|
||||
let bookmarkImg = isBookmarked ? "bookmark.slash" : "bookmark"
|
||||
let bookmarkTxt = isBookmarked ? "Remove\nBookmark" : "Bookmark"
|
||||
let bookmarkTxt = isBookmarked ? NSLocalizedString("Remove Bookmark", comment: "Button text to remove bookmark from a note.") : NSLocalizedString("Add Bookmark", comment: "Button text to add bookmark to a note.")
|
||||
let boomarkCol = isBookmarked ? Color(.red) : col
|
||||
ShareActionButton(img: bookmarkImg, text: NSLocalizedString(bookmarkTxt, comment: "Button to bookmark to note"), col: boomarkCol) {
|
||||
ShareActionButton(img: bookmarkImg, text: bookmarkTxt, col: boomarkCol) {
|
||||
show_share_action = false
|
||||
self.bookmarks.updateBookmark(event)
|
||||
isBookmarked = self.bookmarks.isBookmarked(event)
|
||||
@@ -75,10 +75,10 @@ struct ShareAction: View {
|
||||
}) {
|
||||
Text(NSLocalizedString("Cancel", comment: "Button to cancel a repost."))
|
||||
.frame(minWidth: 300, maxWidth: .infinity, minHeight: 50, maxHeight: 50, alignment: .center)
|
||||
.foregroundColor(colorScheme == .light ? Color("DamusBlack") : Color("DamusWhite"))
|
||||
.foregroundColor(colorScheme == .light ? DamusColors.black : DamusColors.white)
|
||||
.overlay {
|
||||
RoundedRectangle(cornerRadius: 24)
|
||||
.stroke(colorScheme == .light ? Color("DamusMediumGrey") : Color("DamusWhite"), lineWidth: 1)
|
||||
.stroke(colorScheme == .light ? DamusColors.mediumGrey : DamusColors.white, lineWidth: 1)
|
||||
}
|
||||
.padding(EdgeInsets(top: 10, leading: 50, bottom: 25, trailing: 50))
|
||||
}
|
||||
|
||||
@@ -8,72 +8,41 @@
|
||||
import SwiftUI
|
||||
|
||||
struct AddRelayView: View {
|
||||
@Binding var show_add_relay: Bool
|
||||
@Binding var relay: String
|
||||
|
||||
let action: (String?) -> Void
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
Form {
|
||||
Section(NSLocalizedString("Add Relay", comment: "Label for section for adding a relay server.")) {
|
||||
ZStack(alignment: .leading) {
|
||||
HStack{
|
||||
TextField(NSLocalizedString("wss://some.relay.com", comment: "Placeholder example for relay server address."), text: $relay)
|
||||
.padding(2)
|
||||
.padding(.leading, 25)
|
||||
.autocorrectionDisabled(true)
|
||||
.textInputAutocapitalization(.never)
|
||||
|
||||
Label("", systemImage: "xmark.circle.fill")
|
||||
.foregroundColor(.blue)
|
||||
.padding(.trailing, -25.0)
|
||||
.opacity((relay == "") ? 0.0 : 1.0)
|
||||
.onTapGesture {
|
||||
self.relay = ""
|
||||
}
|
||||
}
|
||||
|
||||
Label("", systemImage: "doc.on.clipboard")
|
||||
.padding(.leading, -10)
|
||||
.onTapGesture {
|
||||
if let pastedrelay = UIPasteboard.general.string {
|
||||
self.relay = pastedrelay
|
||||
}
|
||||
}
|
||||
ZStack(alignment: .leading) {
|
||||
HStack{
|
||||
TextField(NSLocalizedString("wss://some.relay.com", comment: "Placeholder example for relay server address."), text: $relay)
|
||||
.padding(2)
|
||||
.padding(.leading, 25)
|
||||
.autocorrectionDisabled(true)
|
||||
.textInputAutocapitalization(.never)
|
||||
|
||||
Label("", systemImage: "xmark.circle.fill")
|
||||
.foregroundColor(.accentColor)
|
||||
.padding(.trailing, -25.0)
|
||||
.opacity((relay == "") ? 0.0 : 1.0)
|
||||
.onTapGesture {
|
||||
self.relay = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VStack {
|
||||
HStack {
|
||||
Button(NSLocalizedString("Cancel", comment: "Button to cancel out of view adding user inputted relay.")) {
|
||||
show_add_relay = false
|
||||
action(nil)
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
|
||||
Spacer()
|
||||
|
||||
Button(NSLocalizedString("Add", comment: "Button to confirm adding user inputted relay.")) {
|
||||
show_add_relay = false
|
||||
action(relay)
|
||||
relay = ""
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.contentShape(Rectangle())
|
||||
Label("", systemImage: "doc.on.clipboard")
|
||||
.padding(.leading, -10)
|
||||
.onTapGesture {
|
||||
if let pastedrelay = UIPasteboard.general.string {
|
||||
self.relay = pastedrelay
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AddRelayView_Previews: PreviewProvider {
|
||||
@State static var show: Bool = true
|
||||
@State static var relay: String = ""
|
||||
|
||||
static var previews: some View {
|
||||
AddRelayView(show_add_relay: $show, relay: $relay, action: {_ in })
|
||||
AddRelayView(relay: $relay)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,23 +15,22 @@ enum ImageUploadResult {
|
||||
case failed(Error?)
|
||||
}
|
||||
|
||||
fileprivate func create_upload_body(imageDataKey: Data, boundary: String, imageUploader: ImageUploader) -> Data {
|
||||
fileprivate func create_upload_body(mediaData: Data, boundary: String, mediaUploader: MediaUploader, mediaToUpload: MediaUpload) -> Data {
|
||||
let body = NSMutableData();
|
||||
let contentType = "image/jpg"
|
||||
let contentType = mediaToUpload.is_image ? "image/jpg" : "video/mp4"
|
||||
body.appendString(string: "Content-Type: multipart/form-data; boundary=\(boundary)\r\n\r\n")
|
||||
body.appendString(string: "--\(boundary)\r\n")
|
||||
body.appendString(string: "Content-Disposition: form-data; name=\(imageUploader.nameParam); filename=\"damus_generic_filename.jpg\"\r\n")
|
||||
body.appendString(string: "Content-Disposition: form-data; name=\(mediaUploader.nameParam); filename=\(mediaToUpload.genericFileName)\r\n")
|
||||
body.appendString(string: "Content-Type: \(contentType)\r\n\r\n")
|
||||
body.append(imageDataKey as Data)
|
||||
body.append(mediaData as Data)
|
||||
body.appendString(string: "\r\n")
|
||||
body.appendString(string: "--\(boundary)--\r\n")
|
||||
return body as Data
|
||||
}
|
||||
|
||||
|
||||
func create_image_upload_request(imageToUpload: UIImage, imageUploader: ImageUploader, progress: URLSessionTaskDelegate) async -> ImageUploadResult {
|
||||
|
||||
guard let url = URL(string: imageUploader.postAPI) else {
|
||||
func create_upload_request(mediaToUpload: MediaUpload, mediaUploader: MediaUploader, progress: URLSessionTaskDelegate) async -> ImageUploadResult {
|
||||
var mediaData: Data?
|
||||
guard let url = URL(string: mediaUploader.postAPI) else {
|
||||
return .failed(nil)
|
||||
}
|
||||
|
||||
@@ -40,13 +39,26 @@ func create_image_upload_request(imageToUpload: UIImage, imageUploader: ImageUpl
|
||||
let boundary = "Boundary-\(UUID().description)"
|
||||
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
|
||||
|
||||
// otherwise convert to jpg
|
||||
guard let jpegData = imageToUpload.jpegData(compressionQuality: 0.8) else {
|
||||
// somehow failed, just return original
|
||||
switch mediaToUpload {
|
||||
case .image(let img):
|
||||
do {
|
||||
mediaData = try Data(contentsOf: img)
|
||||
} catch {
|
||||
return .failed(error)
|
||||
}
|
||||
case .video(let url):
|
||||
do {
|
||||
mediaData = try Data(contentsOf: url)
|
||||
} catch {
|
||||
return .failed(error)
|
||||
}
|
||||
}
|
||||
|
||||
guard let mediaData else {
|
||||
return .failed(nil)
|
||||
}
|
||||
|
||||
request.httpBody = create_upload_body(imageDataKey: jpegData, boundary: boundary, imageUploader: imageUploader)
|
||||
|
||||
request.httpBody = create_upload_body(mediaData: mediaData, boundary: boundary, mediaUploader: mediaUploader, mediaToUpload: mediaToUpload)
|
||||
|
||||
do {
|
||||
let (data, _) = try await URLSession.shared.data(for: request, delegate: progress)
|
||||
@@ -56,8 +68,8 @@ func create_image_upload_request(imageToUpload: UIImage, imageUploader: ImageUpl
|
||||
return .failed(nil)
|
||||
}
|
||||
|
||||
guard let url = imageUploader.getImageURL(from: responseString) else {
|
||||
print("Upload failed getting image url")
|
||||
guard let url = mediaUploader.getMediaURL(from: responseString, mediaIsImage: mediaToUpload.is_image) else {
|
||||
print("Upload failed getting media url")
|
||||
return .failed(nil)
|
||||
}
|
||||
|
||||
@@ -66,7 +78,6 @@ func create_image_upload_request(imageToUpload: UIImage, imageUploader: ImageUpl
|
||||
} catch {
|
||||
return .failed(error)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension PostView {
|
||||
@@ -76,48 +87,63 @@ extension PostView {
|
||||
private var presentationMode
|
||||
|
||||
let sourceType: UIImagePickerController.SourceType
|
||||
let onImagePicked: (UIImage) -> Void
|
||||
let damusState: DamusState
|
||||
let onImagePicked: (URL) -> Void
|
||||
let onVideoPicked: (URL) -> Void
|
||||
|
||||
final class Coordinator: NSObject,
|
||||
UINavigationControllerDelegate,
|
||||
UIImagePickerControllerDelegate {
|
||||
|
||||
@Binding
|
||||
private var presentationMode: PresentationMode
|
||||
final class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
|
||||
@Binding private var presentationMode: PresentationMode
|
||||
private let sourceType: UIImagePickerController.SourceType
|
||||
private let onImagePicked: (UIImage) -> Void
|
||||
private let onImagePicked: (URL) -> Void
|
||||
private let onVideoPicked: (URL) -> Void
|
||||
|
||||
init(presentationMode: Binding<PresentationMode>,
|
||||
sourceType: UIImagePickerController.SourceType,
|
||||
onImagePicked: @escaping (UIImage) -> Void) {
|
||||
onImagePicked: @escaping (URL) -> Void,
|
||||
onVideoPicked: @escaping (URL) -> Void) {
|
||||
_presentationMode = presentationMode
|
||||
self.sourceType = sourceType
|
||||
self.onImagePicked = onImagePicked
|
||||
self.onVideoPicked = onVideoPicked
|
||||
}
|
||||
|
||||
func imagePickerController(_ picker: UIImagePickerController,
|
||||
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
|
||||
let uiImage = info[UIImagePickerController.InfoKey.originalImage] as! UIImage
|
||||
onImagePicked(uiImage)
|
||||
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
|
||||
if let videoURL = info[UIImagePickerController.InfoKey.mediaURL] as? URL {
|
||||
// Handle the selected video
|
||||
onVideoPicked(videoURL)
|
||||
} else if let imageURL = info[UIImagePickerController.InfoKey.imageURL] as? URL {
|
||||
// Handle the selected image
|
||||
self.onImagePicked(imageURL)
|
||||
}
|
||||
presentationMode.dismiss()
|
||||
|
||||
}
|
||||
|
||||
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
|
||||
presentationMode.dismiss()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func makeCoordinator() -> Coordinator {
|
||||
return Coordinator(presentationMode: presentationMode,
|
||||
sourceType: sourceType,
|
||||
onImagePicked: onImagePicked)
|
||||
onImagePicked: { url in
|
||||
// Handle the selected image URL
|
||||
onImagePicked(url)
|
||||
},
|
||||
onVideoPicked: { videoURL in
|
||||
// Handle the selected video URL
|
||||
onVideoPicked(videoURL)
|
||||
})
|
||||
}
|
||||
|
||||
func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker>) -> UIImagePickerController {
|
||||
let picker = UIImagePickerController()
|
||||
picker.sourceType = sourceType
|
||||
let mediaUploader = get_media_uploader(damusState.keypair.pubkey)
|
||||
picker.mediaTypes = ["public.image", "com.compuserve.gif"]
|
||||
if mediaUploader.supportsVideo {
|
||||
picker.mediaTypes.append("public.movie")
|
||||
}
|
||||
picker.delegate = context.coordinator
|
||||
return picker
|
||||
}
|
||||
@@ -138,7 +164,7 @@ extension NSMutableData {
|
||||
}
|
||||
}
|
||||
|
||||
enum ImageUploader: String, CaseIterable, Identifiable {
|
||||
enum MediaUploader: String, CaseIterable, Identifiable {
|
||||
var id: String { self.rawValue }
|
||||
case nostrBuild
|
||||
case nostrImg
|
||||
@@ -152,12 +178,12 @@ enum ImageUploader: String, CaseIterable, Identifiable {
|
||||
}
|
||||
}
|
||||
|
||||
var displayImageUploaderName: String {
|
||||
var supportsVideo: Bool {
|
||||
switch self {
|
||||
case .nostrBuild:
|
||||
return "NostrBuild"
|
||||
return true
|
||||
case .nostrImg:
|
||||
return "NostrImg"
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,7 +213,7 @@ enum ImageUploader: String, CaseIterable, Identifiable {
|
||||
}
|
||||
}
|
||||
|
||||
func getImageURL(from responseString: String) -> String? {
|
||||
func getMediaURL(from responseString: String, mediaIsImage: Bool) -> String? {
|
||||
switch self {
|
||||
case .nostrBuild:
|
||||
guard let startIndex = responseString.range(of: "nostr.build_")?.lowerBound else {
|
||||
@@ -199,7 +225,7 @@ enum ImageUploader: String, CaseIterable, Identifiable {
|
||||
return nil
|
||||
}
|
||||
let nostrBuildImageName = responseString[startIndex..<endIndex]
|
||||
let nostrBuildURL = "https://nostr.build/i/\(nostrBuildImageName)"
|
||||
let nostrBuildURL = mediaIsImage ? "https://nostr.build/i/\(nostrBuildImageName)" : "https://nostr.build/av/\(nostrBuildImageName)"
|
||||
return nostrBuildURL
|
||||
|
||||
case .nostrImg:
|
||||
|
||||
@@ -29,6 +29,8 @@ struct ConfigView: View {
|
||||
@ObservedObject var settings: UserSettingsStore
|
||||
|
||||
let generator = UIImpactFeedbackGenerator(style: .light)
|
||||
|
||||
private let DELETE_KEYWORD = "DELETE"
|
||||
|
||||
init(state: DamusState) {
|
||||
self.state = state
|
||||
@@ -39,7 +41,7 @@ struct ConfigView: View {
|
||||
}
|
||||
|
||||
func textColor() -> Color {
|
||||
colorScheme == .light ? Color("DamusBlack") : Color("DamusWhite")
|
||||
colorScheme == .light ? DamusColors.black : DamusColors.white
|
||||
}
|
||||
|
||||
func authenticateLocally(completion: @escaping (Bool) -> Void) {
|
||||
@@ -153,6 +155,9 @@ struct ConfigView: View {
|
||||
}
|
||||
|
||||
Section(NSLocalizedString("Translations", comment: "Section title for selecting the translation service.")) {
|
||||
Toggle(NSLocalizedString("Show only preferred languages on Universe feed", comment: "Toggle to show notes that are only in the device's preferred languages on the Universe feed and hide notes that are in other languages."), isOn: $settings.show_only_preferred_languages)
|
||||
.toggleStyle(.switch)
|
||||
|
||||
Picker(NSLocalizedString("Service", comment: "Prompt selection of translation service provider."), selection: $settings.translation_service) {
|
||||
ForEach(TranslationService.allCases, id: \.self) { server in
|
||||
Text(server.model.displayName)
|
||||
@@ -197,6 +202,11 @@ struct ConfigView: View {
|
||||
Link(NSLocalizedString("Get API Key", comment: "Button to navigate to DeepL website to get a translation API key."), destination: URL(string: "https://www.deepl.com/pro-api")!)
|
||||
}
|
||||
}
|
||||
|
||||
if settings.translation_service != .none {
|
||||
Toggle(NSLocalizedString("Automatically translate notes", comment: "Toggle to automatically translate notes."), isOn: $settings.auto_translate)
|
||||
.toggleStyle(.switch)
|
||||
}
|
||||
}
|
||||
|
||||
Section(NSLocalizedString("Miscellaneous", comment: "Section header for miscellaneous user configuration")) {
|
||||
@@ -220,8 +230,8 @@ struct ConfigView: View {
|
||||
}
|
||||
|
||||
Picker(NSLocalizedString("Select image uploader", comment: "Prompt selection of user's image uploader"),
|
||||
selection: $settings.default_image_uploader) {
|
||||
ForEach(ImageUploader.allCases, id: \.self) { uploader in
|
||||
selection: $settings.default_media_uploader) {
|
||||
ForEach(MediaUploader.allCases, id: \.self) { uploader in
|
||||
Text(uploader.model.displayName)
|
||||
.tag(uploader.model.tag)
|
||||
}
|
||||
@@ -269,7 +279,7 @@ struct ConfigView: View {
|
||||
}
|
||||
}
|
||||
.alert(NSLocalizedString("Permanently Delete Account", comment: "Alert for deleting the users account."), isPresented: $confirm_delete_account) {
|
||||
TextField(NSLocalizedString("Type DELETE to delete", comment: "Text field prompt asking user to type the word DELETE to confirm that they want to proceed with deleting their account. The all caps lock DELETE word should not be translated. Everything else should."), text: $delete_text)
|
||||
TextField(String(format: NSLocalizedString("Type %@ to delete", comment: "Text field prompt asking user to type DELETE in all caps to confirm that they want to proceed with deleting their account."), DELETE_KEYWORD), text: $delete_text)
|
||||
Button(NSLocalizedString("Cancel", comment: "Cancel deleting the user."), role: .cancel) {
|
||||
confirm_delete_account = false
|
||||
}
|
||||
@@ -278,7 +288,7 @@ struct ConfigView: View {
|
||||
return
|
||||
}
|
||||
|
||||
guard delete_text == "DELETE" else {
|
||||
guard delete_text == DELETE_KEYWORD else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ struct EditMetadataView: View {
|
||||
}
|
||||
|
||||
func imageBorderColor() -> Color {
|
||||
colorScheme == .light ? Color("DamusWhite") : Color("DamusBlack")
|
||||
colorScheme == .light ? DamusColors.white : DamusColors.black
|
||||
}
|
||||
|
||||
func save() {
|
||||
|
||||
@@ -14,7 +14,6 @@ struct MutedEventView: View {
|
||||
|
||||
let selected: Bool
|
||||
@State var shown: Bool
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
|
||||
init(damus_state: DamusState, event: NostrEvent, scroller: ScrollViewProxy?, selected: Bool) {
|
||||
self.damus_state = damus_state
|
||||
@@ -28,14 +27,10 @@ struct MutedEventView: View {
|
||||
return !should_show_event(contacts: damus_state.contacts, ev: event)
|
||||
}
|
||||
|
||||
var FillColor: Color {
|
||||
colorScheme == .light ? Color("DamusLightGrey") : Color("DamusDarkGrey")
|
||||
}
|
||||
|
||||
var MutedBox: some View {
|
||||
ZStack {
|
||||
RoundedRectangle(cornerRadius: 20)
|
||||
.foregroundColor(FillColor)
|
||||
.foregroundColor(DamusColors.adaptableGrey)
|
||||
|
||||
HStack {
|
||||
Text("Post from a user you've blocked", comment: "Text to indicate that what is being shown is a post from a user who has been blocked.")
|
||||
|
||||
@@ -20,7 +20,7 @@ struct ZapEvent: View {
|
||||
|
||||
if zap.private_request != nil {
|
||||
Image(systemName: "lock.fill")
|
||||
.foregroundColor(Color("DamusGreen"))
|
||||
.foregroundColor(DamusColors.green)
|
||||
.help(NSLocalizedString("Only you can see this message and who sent it.", comment: "Help text on green lock icon that explains that only the current user can see the message of a zap event and who sent the zap."))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,11 +50,11 @@ struct FollowButtonView: View {
|
||||
}
|
||||
|
||||
func filledTextColor() -> Color {
|
||||
colorScheme == .light ? Color("DamusWhite") : Color("DamusBlack")
|
||||
colorScheme == .light ? DamusColors.white : DamusColors.black
|
||||
}
|
||||
|
||||
func fillColor() -> Color {
|
||||
colorScheme == .light ? Color("DamusBlack") : Color("DamusWhite")
|
||||
colorScheme == .light ? DamusColors.black : DamusColors.white
|
||||
}
|
||||
|
||||
func emptyColor() -> Color {
|
||||
@@ -62,7 +62,7 @@ struct FollowButtonView: View {
|
||||
}
|
||||
|
||||
func borderColor() -> Color {
|
||||
colorScheme == .light ? Color("DamusDarkGrey") : Color("DamusLightGrey")
|
||||
colorScheme == .light ? DamusColors.darkGrey : DamusColors.lightGrey
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ struct FollowersView: View {
|
||||
LazyVStack(alignment: .leading) {
|
||||
ForEach(followers.contacts ?? [], id: \.self) { pk in
|
||||
FollowUserView(target: .pubkey(pk), damus_state: damus_state)
|
||||
Divider()
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
@@ -45,7 +46,6 @@ struct FollowersView: View {
|
||||
followers.unsubscribe()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct FollowingView: View {
|
||||
|
||||
@@ -16,26 +16,6 @@ struct ImageView: View {
|
||||
@State private var selectedIndex = 0
|
||||
@State var showMenu = true
|
||||
|
||||
var navBarView: some View {
|
||||
VStack {
|
||||
HStack {
|
||||
/*
|
||||
Text(urls[selectedIndex]?.lastPathComponent ?? "")
|
||||
.bold()
|
||||
*/
|
||||
|
||||
Spacer()
|
||||
|
||||
Button(action: {
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}, label: {
|
||||
Image(systemName: "xmark")
|
||||
})
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
var tabViewIndicator: some View {
|
||||
HStack(spacing: 10) {
|
||||
ForEach(urls.indices, id: \.self) { index in
|
||||
@@ -80,7 +60,7 @@ struct ImageView: View {
|
||||
.overlay(
|
||||
VStack {
|
||||
if showMenu {
|
||||
navBarView
|
||||
NavDismissBarView()
|
||||
Spacer()
|
||||
|
||||
if (urls.count > 1) {
|
||||
|
||||
@@ -39,14 +39,11 @@ struct ProfileImageContainerView: View {
|
||||
}
|
||||
}
|
||||
|
||||
struct ProfileZoomView: View {
|
||||
|
||||
let pubkey: String
|
||||
let profiles: Profiles
|
||||
struct NavDismissBarView: View {
|
||||
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
|
||||
var navBarView: some View {
|
||||
var body: some View {
|
||||
HStack {
|
||||
Button(action: {
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
@@ -61,6 +58,14 @@ struct ProfileZoomView: View {
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
struct ProfilePicImageView: View {
|
||||
|
||||
let pubkey: String
|
||||
let profiles: Profiles
|
||||
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
@@ -79,7 +84,7 @@ struct ProfileZoomView: View {
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}))
|
||||
}
|
||||
.overlay(navBarView, alignment: .top)
|
||||
.overlay(NavDismissBarView(), alignment: .top)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +92,7 @@ struct ProfileZoomView_Previews: PreviewProvider {
|
||||
static let pubkey = "ca48854ac6555fed8e439ebb4fa2d928410e0eef13fa41164ec45aaaa132d846"
|
||||
|
||||
static var previews: some View {
|
||||
ProfileZoomView(
|
||||
ProfilePicImageView(
|
||||
pubkey: pubkey,
|
||||
profiles: make_preview_profiles(pubkey))
|
||||
}
|
||||
@@ -47,7 +47,7 @@ struct TabButton: View {
|
||||
.frame(width: 10, height: 10, alignment: .topTrailing)
|
||||
.alignmentGuide(VerticalAlignment.center) { a in a.height + 2.0 }
|
||||
.alignmentGuide(HorizontalAlignment.center) { a in a.width - 12.0 }
|
||||
.foregroundColor(.accentColor)
|
||||
.foregroundStyle(LINEAR_GRADIENT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,10 @@ struct NoteContentView: View {
|
||||
var invoicesView: some View {
|
||||
InvoicesView(our_pubkey: damus_state.keypair.pubkey, invoices: artifacts.invoices)
|
||||
}
|
||||
|
||||
var translateView: some View {
|
||||
TranslateView(damus_state: damus_state, event: event)
|
||||
}
|
||||
|
||||
var previewView: some View {
|
||||
Group {
|
||||
@@ -82,7 +86,6 @@ struct NoteContentView: View {
|
||||
VStack(alignment: .leading) {
|
||||
if size == .selected {
|
||||
SelectableText(attributedString: artifacts.content)
|
||||
TranslateView(damus_state: damus_state, event: event)
|
||||
} else {
|
||||
if with_padding {
|
||||
truncatedText
|
||||
@@ -92,6 +95,15 @@ struct NoteContentView: View {
|
||||
}
|
||||
}
|
||||
|
||||
if size == .selected || damus_state.settings.auto_translate {
|
||||
if with_padding {
|
||||
translateView
|
||||
.padding(.horizontal)
|
||||
} else {
|
||||
translateView
|
||||
}
|
||||
}
|
||||
|
||||
if show_images && artifacts.images.count > 0 {
|
||||
ImageCarousel(urls: artifacts.images)
|
||||
} else if !show_images && artifacts.images.count > 0 {
|
||||
@@ -178,14 +190,14 @@ struct NoteContentView: View {
|
||||
func hashtag_str(_ htag: String) -> AttributedString {
|
||||
var attributedString = AttributedString(stringLiteral: "#\(htag)")
|
||||
attributedString.link = URL(string: "damus:t:\(htag)")
|
||||
attributedString.foregroundColor = Color("DamusPurple")
|
||||
attributedString.foregroundColor = DamusColors.purple
|
||||
return attributedString
|
||||
}
|
||||
|
||||
func url_str(_ url: URL) -> AttributedString {
|
||||
var attributedString = AttributedString(stringLiteral: url.absoluteString)
|
||||
attributedString.link = url
|
||||
attributedString.foregroundColor = Color("DamusPurple")
|
||||
attributedString.foregroundColor = DamusColors.purple
|
||||
return attributedString
|
||||
}
|
||||
|
||||
@@ -197,13 +209,13 @@ func mention_str(_ m: Mention, profiles: Profiles) -> AttributedString {
|
||||
let disp = Profile.displayName(profile: profile, pubkey: pk).username
|
||||
var attributedString = AttributedString(stringLiteral: "@\(disp)")
|
||||
attributedString.link = URL(string: "damus:\(encode_pubkey_uri(m.ref))")
|
||||
attributedString.foregroundColor = Color("DamusPurple")
|
||||
attributedString.foregroundColor = DamusColors.purple
|
||||
return attributedString
|
||||
case .event:
|
||||
let bevid = bech32_note_id(m.ref.ref_id) ?? m.ref.ref_id
|
||||
var attributedString = AttributedString(stringLiteral: "@\(abbrev_pubkey(bevid))")
|
||||
attributedString.link = URL(string: "damus:\(encode_event_id_uri(m.ref))")
|
||||
attributedString.foregroundColor = Color("DamusPurple")
|
||||
attributedString.foregroundColor = DamusColors.purple
|
||||
return attributedString
|
||||
}
|
||||
}
|
||||
@@ -217,16 +229,6 @@ struct NoteContentView_Previews: PreviewProvider {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
extension View {
|
||||
func translate_button_style() -> some View {
|
||||
return self
|
||||
.font(.footnote)
|
||||
.contentShape(Rectangle())
|
||||
.padding([.top, .bottom], 10)
|
||||
}
|
||||
}
|
||||
|
||||
struct NoteArtifacts {
|
||||
let content: AttributedString
|
||||
let images: [URL]
|
||||
@@ -240,6 +242,7 @@ struct NoteArtifacts {
|
||||
|
||||
func render_note_content(ev: NostrEvent, profiles: Profiles, privkey: String?) -> NoteArtifacts {
|
||||
let blocks = ev.blocks(privkey)
|
||||
|
||||
return render_blocks(blocks: blocks, profiles: profiles, privkey: privkey)
|
||||
}
|
||||
|
||||
@@ -247,9 +250,17 @@ func render_blocks(blocks: [Block], profiles: Profiles, privkey: String?) -> Not
|
||||
var invoices: [Invoice] = []
|
||||
var img_urls: [URL] = []
|
||||
var link_urls: [URL] = []
|
||||
|
||||
let one_note_ref = blocks
|
||||
.filter({ $0.is_note_mention })
|
||||
.count == 1
|
||||
|
||||
let txt: AttributedString = blocks.reduce("") { str, block in
|
||||
switch block {
|
||||
case .mention(let m):
|
||||
if m.type == .event && one_note_ref {
|
||||
return str
|
||||
}
|
||||
return str + mention_str(m, profiles: profiles)
|
||||
case .text(let txt):
|
||||
return str + AttributedString(stringLiteral: txt)
|
||||
|
||||
@@ -184,12 +184,12 @@ struct EventGroupView: View {
|
||||
switch group {
|
||||
case .repost:
|
||||
Image(systemName: "arrow.2.squarepath")
|
||||
.foregroundColor(Color("DamusGreen"))
|
||||
.foregroundColor(DamusColors.green)
|
||||
case .reaction:
|
||||
Image("shaka-full")
|
||||
.resizable()
|
||||
.frame(width: 24, height: 24)
|
||||
.foregroundColor(.accentColor)
|
||||
LINEAR_GRADIENT
|
||||
.mask(Image("shaka-full")
|
||||
.resizable()
|
||||
).frame(width: 24, height: 24)
|
||||
case .profile_zap(let zapgrp):
|
||||
ZapIcon(zapgrp)
|
||||
case .zap(let zapgrp):
|
||||
@@ -206,17 +206,20 @@ struct EventGroupView: View {
|
||||
VStack(alignment: .leading) {
|
||||
ProfilePicturesView(state: state, events: group.events)
|
||||
|
||||
GroupDescription
|
||||
|
||||
if let event {
|
||||
let thread = ThreadModel(event: event, damus_state: state)
|
||||
let dest = ThreadView(state: state, thread: thread)
|
||||
NavigationLink(destination: dest) {
|
||||
Text(render_note_content(ev: event, profiles: state.profiles, privkey: state.keypair.privkey).content)
|
||||
.padding([.top], 1)
|
||||
.foregroundColor(.gray)
|
||||
VStack(alignment: .leading) {
|
||||
GroupDescription
|
||||
EventBody(damus_state: state, event: event, size: .normal, options: [])
|
||||
.padding([.top], 1)
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
} else {
|
||||
GroupDescription
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ import SwiftUI
|
||||
|
||||
let BUTTON_SIZE = 57.0
|
||||
let LINEAR_GRADIENT = LinearGradient(gradient: Gradient(colors: [
|
||||
Color("DamusPurple"),
|
||||
Color("DamusBlue")
|
||||
DamusColors.purple,
|
||||
DamusColors.blue
|
||||
]), startPoint: .topTrailing, endPoint: .bottomTrailing)
|
||||
|
||||
func PostButton(action: @escaping () -> ()) -> some View {
|
||||
|
||||
@@ -19,6 +19,7 @@ struct PostView: View {
|
||||
@FocusState var focus: Bool
|
||||
@State var showPrivateKeyWarning: Bool = false
|
||||
@State var attach_media: Bool = false
|
||||
@State var attach_camera: Bool = false
|
||||
@State var error: String? = nil
|
||||
|
||||
@StateObject var image_upload: ImageUploadModel = ImageUploadModel()
|
||||
@@ -80,11 +81,20 @@ struct PostView: View {
|
||||
})
|
||||
}
|
||||
|
||||
var CameraButton: some View {
|
||||
Button(action: {
|
||||
attach_camera = true
|
||||
}, label: {
|
||||
Image(systemName: "camera")
|
||||
})
|
||||
}
|
||||
|
||||
var AttachmentBar: some View {
|
||||
HStack(alignment: .center) {
|
||||
ImageButton
|
||||
.disabled(image_upload.progress != nil)
|
||||
CameraButton
|
||||
}
|
||||
.disabled(image_upload.progress != nil)
|
||||
}
|
||||
|
||||
var PostButton: some View {
|
||||
@@ -168,11 +178,11 @@ struct PostView: View {
|
||||
post = combinedAttributedString
|
||||
}
|
||||
|
||||
func handle_upload(image: UIImage) {
|
||||
let uploader = get_image_uploader(damus_state.pubkey)
|
||||
func handle_upload(media: MediaUpload) {
|
||||
let uploader = get_media_uploader(damus_state.pubkey)
|
||||
|
||||
Task.init {
|
||||
let res = await image_upload.start(img: image, uploader: uploader)
|
||||
let res = await image_upload.start(media: media, uploader: uploader)
|
||||
|
||||
switch res {
|
||||
case .success(let url):
|
||||
@@ -215,8 +225,17 @@ struct PostView: View {
|
||||
}
|
||||
.padding()
|
||||
.sheet(isPresented: $attach_media) {
|
||||
ImagePicker(sourceType: .photoLibrary) { img in
|
||||
handle_upload(image: img)
|
||||
ImagePicker(sourceType: .photoLibrary, damusState: damus_state) { img in
|
||||
handle_upload(media: .image(img))
|
||||
} onVideoPicked: { url in
|
||||
handle_upload(media: .video(url))
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $attach_camera) {
|
||||
ImagePicker(sourceType: .camera, damusState: damus_state) { img in
|
||||
handle_upload(media: .image(img))
|
||||
} onVideoPicked: { url in
|
||||
handle_upload(media: .video(url))
|
||||
}
|
||||
}
|
||||
.onAppear() {
|
||||
|
||||
@@ -8,11 +8,6 @@
|
||||
import SwiftUI
|
||||
|
||||
struct FollowsYou: View {
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
|
||||
var fill_color: Color {
|
||||
colorScheme == .light ? Color("DamusLightGrey") : Color("DamusDarkGrey")
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Text("Follows you", comment: "Text to indicate that a user is following your profile.")
|
||||
@@ -21,7 +16,7 @@ struct FollowsYou: View {
|
||||
.foregroundColor(.gray)
|
||||
.background {
|
||||
RoundedRectangle(cornerRadius: 5.0)
|
||||
.foregroundColor(fill_color)
|
||||
.foregroundColor(DamusColors.adaptableGrey)
|
||||
}
|
||||
.font(.footnote)
|
||||
}
|
||||
|
||||
@@ -60,11 +60,7 @@ struct ProfileName: View {
|
||||
var current_nip05: NIP05? {
|
||||
nip05 ?? damus_state.profiles.is_validated(pubkey)
|
||||
}
|
||||
|
||||
var nip05_color: Color {
|
||||
return get_nip05_color(pubkey: pubkey, contacts: damus_state.contacts)
|
||||
}
|
||||
|
||||
|
||||
var current_display_name: DisplayName {
|
||||
return display_name ?? Profile.displayName(profile: profile, pubkey: pubkey)
|
||||
}
|
||||
|
||||
@@ -87,11 +87,11 @@ struct EditButton: View {
|
||||
}
|
||||
|
||||
func fillColor() -> Color {
|
||||
colorScheme == .light ? Color("DamusBlack") : Color("DamusWhite")
|
||||
colorScheme == .light ? DamusColors.black : DamusColors.white
|
||||
}
|
||||
|
||||
func borderColor() -> Color {
|
||||
colorScheme == .light ? Color("DamusBlack") : Color("DamusWhite")
|
||||
colorScheme == .light ? DamusColors.black : DamusColors.white
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,12 +143,8 @@ struct ProfileView: View {
|
||||
@Environment(\.openURL) var openURL
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
|
||||
func fillColor() -> Color {
|
||||
colorScheme == .light ? Color("DamusLightGrey") : Color("DamusDarkGrey")
|
||||
}
|
||||
|
||||
func imageBorderColor() -> Color {
|
||||
colorScheme == .light ? Color("DamusWhite") : Color("DamusBlack")
|
||||
colorScheme == .light ? DamusColors.white : DamusColors.black
|
||||
}
|
||||
|
||||
func bannerBlurViewOpacity() -> Double {
|
||||
@@ -240,7 +236,7 @@ struct ProfileView: View {
|
||||
}
|
||||
.padding(.top, 5)
|
||||
.padding(.horizontal)
|
||||
.accentColor(Color("DamusWhite"))
|
||||
.accentColor(DamusColors.white)
|
||||
}
|
||||
|
||||
func lnButton(lnurl: String, profile: Profile) -> some View {
|
||||
@@ -327,7 +323,7 @@ struct ProfileView: View {
|
||||
is_zoomed.toggle()
|
||||
}
|
||||
.fullScreenCover(isPresented: $is_zoomed) {
|
||||
ProfileZoomView(pubkey: profile.pubkey, profiles: damus_state.profiles) }
|
||||
ProfilePicImageView(pubkey: profile.pubkey, profiles: damus_state.profiles) }
|
||||
|
||||
Spacer()
|
||||
|
||||
@@ -496,12 +492,8 @@ struct KeyView: View {
|
||||
|
||||
@State private var isCopied = false
|
||||
|
||||
func fillColor() -> Color {
|
||||
colorScheme == .light ? Color("DamusLightGrey") : Color("DamusDarkGrey")
|
||||
}
|
||||
|
||||
func keyColor() -> Color {
|
||||
colorScheme == .light ? Color("DamusBlack") : Color("DamusWhite")
|
||||
colorScheme == .light ? DamusColors.black : DamusColors.white
|
||||
}
|
||||
|
||||
private func copyPubkey(_ pubkey: String) {
|
||||
@@ -538,7 +530,7 @@ struct KeyView: View {
|
||||
}
|
||||
.padding(2)
|
||||
.padding([.leading, .trailing], 3)
|
||||
.background(RoundedRectangle(cornerRadius: 11).foregroundColor(fillColor()))
|
||||
.background(RoundedRectangle(cornerRadius: 11).foregroundColor(DamusColors.adaptableGrey))
|
||||
|
||||
if isCopied != true {
|
||||
Button {
|
||||
@@ -563,7 +555,7 @@ struct KeyView: View {
|
||||
.font(.footnote)
|
||||
.layoutPriority(1)
|
||||
}
|
||||
.foregroundColor(Color("DamusGreen"))
|
||||
.foregroundColor(DamusColors.green)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,23 +42,23 @@ struct QRCodeView: View {
|
||||
let profile = damus_state.profiles.lookup(id: damus_state.pubkey)
|
||||
|
||||
if (damus_state.profiles.lookup(id: damus_state.pubkey)?.picture) != nil {
|
||||
ProfilePicView(pubkey: damus_state.pubkey, size: 90.0, highlight: .custom(Color("DamusWhite"), 4.0), profiles: damus_state.profiles)
|
||||
ProfilePicView(pubkey: damus_state.pubkey, size: 90.0, highlight: .custom(DamusColors.white, 4.0), profiles: damus_state.profiles)
|
||||
.padding(.top, 50)
|
||||
} else {
|
||||
Image(systemName: "person.fill")
|
||||
.font(.system(size: 60))
|
||||
.foregroundColor(Color("DamusWhite"))
|
||||
.foregroundColor(DamusColors.white)
|
||||
.padding(.top, 50)
|
||||
}
|
||||
|
||||
if let display_name = profile?.display_name {
|
||||
Text(display_name)
|
||||
.foregroundColor(Color("DamusWhite"))
|
||||
.foregroundColor(DamusColors.white)
|
||||
.font(.system(size: 24, weight: .heavy))
|
||||
}
|
||||
if let name = profile?.name {
|
||||
Text("@" + name)
|
||||
.foregroundColor(Color("DamusWhite"))
|
||||
.foregroundColor(DamusColors.white)
|
||||
.font(.body)
|
||||
}
|
||||
|
||||
@@ -73,19 +73,19 @@ struct QRCodeView: View {
|
||||
.padding()
|
||||
.cornerRadius(10)
|
||||
.overlay(RoundedRectangle(cornerRadius: 10)
|
||||
.stroke(Color("DamusWhite"), lineWidth: 1))
|
||||
.stroke(DamusColors.white, lineWidth: 1))
|
||||
.shadow(radius: 10)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("Follow me on nostr", comment: "Text on QR code view to prompt viewer looking at screen to follow the user.")
|
||||
.foregroundColor(Color("DamusWhite"))
|
||||
.foregroundColor(DamusColors.white)
|
||||
.font(.system(size: 24, weight: .heavy))
|
||||
.padding(.top)
|
||||
|
||||
Text("Scan the code", comment: "Text on QR code view to prompt viewer to scan the QR code on screen with their device camera.")
|
||||
.foregroundColor(Color("DamusWhite"))
|
||||
.foregroundColor(DamusColors.white)
|
||||
.font(.system(size: 18, weight: .ultraLight))
|
||||
|
||||
Spacer()
|
||||
|
||||
@@ -12,22 +12,33 @@ struct RecommendedRelayView: View {
|
||||
let relay: String
|
||||
let add_button: Bool
|
||||
|
||||
init(damus: DamusState, relay: String) {
|
||||
@Binding var showActionButtons: Bool
|
||||
|
||||
init(damus: DamusState, relay: String, showActionButtons: Binding<Bool>) {
|
||||
self.damus = damus
|
||||
self.relay = relay
|
||||
self.add_button = true
|
||||
self._showActionButtons = showActionButtons
|
||||
}
|
||||
|
||||
init(damus: DamusState, relay: String, add_button: Bool) {
|
||||
init(damus: DamusState, relay: String, add_button: Bool, showActionButtons: Binding<Bool>) {
|
||||
self.damus = damus
|
||||
self.relay = relay
|
||||
self.add_button = add_button
|
||||
self._showActionButtons = showActionButtons
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
HStack {
|
||||
if let privkey = damus.keypair.privkey {
|
||||
if showActionButtons && add_button {
|
||||
AddButton(privkey: privkey, showText: false)
|
||||
}
|
||||
}
|
||||
|
||||
RelayType(is_paid: damus.relay_metadata.lookup(relay_id: relay)?.is_paid ?? false)
|
||||
|
||||
Text(relay).layoutPriority(1)
|
||||
|
||||
if let meta = damus.relay_metadata.lookup(relay_id: relay) {
|
||||
@@ -37,41 +48,75 @@ struct RecommendedRelayView: View {
|
||||
EmptyView()
|
||||
}
|
||||
.opacity(0.0)
|
||||
.disabled(showActionButtons)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(systemName: "info.circle")
|
||||
.font(.system(size: 20, weight: .regular))
|
||||
.foregroundColor(Color.accentColor)
|
||||
} else {
|
||||
Spacer()
|
||||
|
||||
Image(systemName: "questionmark.circle")
|
||||
.font(.system(size: 20, weight: .regular))
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
}
|
||||
.swipeActions {
|
||||
if add_button {
|
||||
if let privkey = damus.keypair.privkey {
|
||||
AddAction(privkey: privkey)
|
||||
AddButton(privkey: privkey, showText: false)
|
||||
.tint(.accentColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
.contextMenu {
|
||||
CopyAction(relay: relay)
|
||||
|
||||
if let privkey = damus.keypair.privkey {
|
||||
AddButton(privkey: privkey, showText: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func AddAction(privkey: String) -> some View {
|
||||
func CopyAction(relay: String) -> some View {
|
||||
Button {
|
||||
guard let ev_before_add = damus.contacts.event else {
|
||||
return
|
||||
}
|
||||
guard let ev_after_add = add_relay(ev: ev_before_add, privkey: privkey, current_relays: damus.pool.descriptors, relay: relay, info: .rw) else {
|
||||
return
|
||||
}
|
||||
process_contact_event(state: damus, ev: ev_after_add)
|
||||
damus.pool.send(.event(ev_after_add))
|
||||
UIPasteboard.general.setValue(relay, forPasteboardType: "public.plain-text")
|
||||
} label: {
|
||||
Label(NSLocalizedString("Add Relay", comment: "Button to add recommended relay server."), systemImage: "plus.circle")
|
||||
Label(NSLocalizedString("Copy", comment: "Button to copy a relay server address."), systemImage: "doc.on.doc")
|
||||
}
|
||||
.tint(.accentColor)
|
||||
}
|
||||
|
||||
func AddButton(privkey: String, showText: Bool) -> some View {
|
||||
Button(action: {
|
||||
add_action(privkey: privkey)
|
||||
}) {
|
||||
if showText {
|
||||
Text(NSLocalizedString("Connect", comment: "Button to connect to recommended relay server."))
|
||||
}
|
||||
Image(systemName: "plus.circle.fill")
|
||||
.font(.system(size: 20, weight: .medium))
|
||||
.foregroundColor(.accentColor)
|
||||
.padding(.leading, 5)
|
||||
}
|
||||
}
|
||||
|
||||
func add_action(privkey: String) {
|
||||
guard let ev_before_add = damus.contacts.event else {
|
||||
return
|
||||
}
|
||||
guard let ev_after_add = add_relay(ev: ev_before_add, privkey: privkey, current_relays: damus.pool.descriptors, relay: relay, info: .rw) else {
|
||||
return
|
||||
}
|
||||
process_contact_event(state: damus, ev: ev_after_add)
|
||||
damus.pool.send(.event(ev_after_add))
|
||||
}
|
||||
}
|
||||
|
||||
struct RecommendedRelayView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
RecommendedRelayView(damus: test_damus_state(), relay: "wss://relay.damus.io")
|
||||
RecommendedRelayView(damus: test_damus_state(), relay: "wss://relay.damus.io", showActionButtons: .constant(false))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ import SwiftUI
|
||||
struct RelayConfigView: View {
|
||||
let state: DamusState
|
||||
@State var new_relay: String = ""
|
||||
@State var show_add_relay: Bool = false
|
||||
@State var relays: [RelayDescriptor]
|
||||
@State private var showActionButtons = false
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
@@ -37,72 +37,122 @@ struct RelayConfigView: View {
|
||||
.onReceive(handle_notify(.switched_timeline)) { _ in
|
||||
dismiss()
|
||||
}
|
||||
.sheet(isPresented: $show_add_relay) {
|
||||
AddRelayView(show_add_relay: $show_add_relay, relay: $new_relay) { m_relay in
|
||||
guard var relay = m_relay else {
|
||||
return
|
||||
}
|
||||
|
||||
if relay.starts(with: "wss://") == false && relay.starts(with: "ws://") == false {
|
||||
relay = "wss://" + relay
|
||||
}
|
||||
|
||||
if relay.hasSuffix("/") {
|
||||
relay.removeLast();
|
||||
}
|
||||
|
||||
guard let url = URL(string: relay) else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let ev = state.contacts.event else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let privkey = state.keypair.privkey else {
|
||||
return
|
||||
}
|
||||
|
||||
let info = RelayInfo.rw
|
||||
|
||||
guard (try? state.pool.add_relay(url, info: info)) != nil else {
|
||||
return
|
||||
}
|
||||
|
||||
state.pool.connect(to: [relay])
|
||||
|
||||
guard let new_ev = add_relay(ev: ev, privkey: privkey, current_relays: state.pool.descriptors, relay: relay, info: info) else {
|
||||
return
|
||||
}
|
||||
|
||||
process_contact_event(state: state, ev: ev)
|
||||
|
||||
state.pool.send(.event(new_ev))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var MainContent: some View {
|
||||
Form {
|
||||
Section {
|
||||
List(Array(relays), id: \.url) { relay in
|
||||
RelayView(state: state, relay: relay.url.absoluteString)
|
||||
}
|
||||
AddRelayView(relay: $new_relay)
|
||||
} header: {
|
||||
HStack {
|
||||
Text("Relays", comment: "Header text for relay server list for configuration.")
|
||||
Spacer()
|
||||
Button(action: { show_add_relay = true }) {
|
||||
Image(systemName: "plus")
|
||||
.foregroundColor(.accentColor)
|
||||
Text(NSLocalizedString("Connect To Relay", comment: "Label for section for adding a relay server."))
|
||||
.font(.system(size: 18, weight: .heavy))
|
||||
.padding(.bottom, 5)
|
||||
}
|
||||
} footer: {
|
||||
VStack {
|
||||
HStack {
|
||||
Spacer()
|
||||
if(!new_relay.isEmpty) {
|
||||
Button(NSLocalizedString("Cancel", comment: "Button to cancel out of view adding user inputted relay.")) {
|
||||
new_relay = ""
|
||||
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
|
||||
}
|
||||
.font(.system(size: 14, weight: .bold))
|
||||
.frame(width: 80, height: 30)
|
||||
.foregroundColor(.white)
|
||||
.background(LINEAR_GRADIENT)
|
||||
.clipShape(Capsule())
|
||||
.padding(EdgeInsets(top: 15, leading: 0, bottom: 0, trailing: 0))
|
||||
|
||||
Button(NSLocalizedString("Add", comment: "Button to confirm adding user inputted relay.")) {
|
||||
|
||||
if new_relay.starts(with: "wss://") == false && new_relay.starts(with: "ws://") == false {
|
||||
new_relay = "wss://" + new_relay
|
||||
}
|
||||
|
||||
if new_relay.hasSuffix("/") {
|
||||
new_relay.removeLast();
|
||||
}
|
||||
|
||||
guard let url = URL(string: new_relay) else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let ev = state.contacts.event else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let privkey = state.keypair.privkey else {
|
||||
return
|
||||
}
|
||||
|
||||
let info = RelayInfo.rw
|
||||
|
||||
guard (try? state.pool.add_relay(url, info: info)) != nil else {
|
||||
return
|
||||
}
|
||||
|
||||
state.pool.connect(to: [new_relay])
|
||||
|
||||
guard let new_ev = add_relay(ev: ev, privkey: privkey, current_relays: state.pool.descriptors, relay: new_relay, info: info) else {
|
||||
return
|
||||
}
|
||||
|
||||
process_contact_event(state: state, ev: ev)
|
||||
|
||||
state.pool.send(.event(new_ev))
|
||||
|
||||
new_relay = ""
|
||||
|
||||
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
|
||||
}
|
||||
.font(.system(size: 14, weight: .bold))
|
||||
.frame(width: 80, height: 30)
|
||||
.foregroundColor(.white)
|
||||
.background(LINEAR_GRADIENT)
|
||||
.clipShape(Capsule())
|
||||
.padding(EdgeInsets(top: 15, leading: 0, bottom: 0, trailing: 0))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Section {
|
||||
List(Array(relays), id: \.url) { relay in
|
||||
RelayView(state: state, relay: relay.url.absoluteString, showActionButtons: $showActionButtons)
|
||||
}
|
||||
} header: {
|
||||
HStack {
|
||||
Text(NSLocalizedString("Connected Relays", comment: "Section title for relay servers that are connected."))
|
||||
.font(.system(size: 18, weight: .heavy))
|
||||
.padding(.bottom, 5)
|
||||
}
|
||||
}
|
||||
|
||||
if recommended.count > 0 {
|
||||
Section(NSLocalizedString("Recommended Relays", comment: "Section title for recommend relay servers that could be added as part of configuration")) {
|
||||
Section {
|
||||
List(recommended, id: \.url) { r in
|
||||
RecommendedRelayView(damus: state, relay: r.url.absoluteString)
|
||||
RecommendedRelayView(damus: state, relay: r.url.absoluteString, showActionButtons: $showActionButtons)
|
||||
}
|
||||
} header: {
|
||||
Text(NSLocalizedString("Recommended Relays", comment: "Section title for recommend relay servers that could be added as part of configuration"))
|
||||
.font(.system(size: 18, weight: .heavy))
|
||||
.padding(.bottom, 5)
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle(NSLocalizedString("Relays", comment: "Title of relays view"))
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
.toolbar {
|
||||
if state.keypair.privkey != nil {
|
||||
if showActionButtons {
|
||||
Button("Done") {
|
||||
showActionButtons.toggle()
|
||||
}
|
||||
} else {
|
||||
Button("Edit") {
|
||||
showActionButtons.toggle()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,15 @@ struct RelayDetailView: View {
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
func check_connection() -> Bool {
|
||||
for relay in state.pool.relays {
|
||||
if relay.id == self.relay {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func FieldText(_ str: String?) -> some View {
|
||||
Text(str ?? "No data available")
|
||||
}
|
||||
@@ -23,6 +32,42 @@ struct RelayDetailView: View {
|
||||
var body: some View {
|
||||
Group {
|
||||
Form {
|
||||
|
||||
if let privkey = state.keypair.privkey {
|
||||
if check_connection() {
|
||||
Button(action: {
|
||||
guard let ev = state.contacts.event else {
|
||||
return
|
||||
}
|
||||
|
||||
let descriptors = state.pool.descriptors
|
||||
guard let new_ev = remove_relay( ev: ev, current_relays: descriptors, privkey: privkey, relay: relay) else {
|
||||
return
|
||||
}
|
||||
|
||||
process_contact_event(state: state, ev: new_ev)
|
||||
state.pool.send(.event(new_ev))
|
||||
dismiss()
|
||||
}) {
|
||||
Text("Disconnect From Relay", comment: "Button to disconnect from the relay.")
|
||||
}
|
||||
} else {
|
||||
Button(action: {
|
||||
guard let ev_before_add = state.contacts.event else {
|
||||
return
|
||||
}
|
||||
guard let ev_after_add = add_relay(ev: ev_before_add, privkey: privkey, current_relays: state.pool.descriptors, relay: relay, info: .rw) else {
|
||||
return
|
||||
}
|
||||
process_contact_event(state: state, ev: ev_after_add)
|
||||
state.pool.send(.event(ev_after_add))
|
||||
dismiss()
|
||||
}) {
|
||||
Text("Connect To Relay", comment: "Button to connect to the relay.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let pubkey = nip11.pubkey {
|
||||
Section(NSLocalizedString("Admin", comment: "Label to display relay contact user.")) {
|
||||
UserView(damus_state: state, pubkey: pubkey)
|
||||
@@ -68,6 +113,7 @@ struct RelayDetailView: View {
|
||||
dismiss()
|
||||
}
|
||||
.navigationTitle(nip11.name ?? "")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
|
||||
private func nipsList(nips: [Int]) -> AttributedString {
|
||||
|
||||
@@ -14,16 +14,20 @@ struct RelayStatus: View {
|
||||
let timer = Timer.publish(every: 2, on: .main, in: .common).autoconnect()
|
||||
|
||||
@State var conn_color: Color = .gray
|
||||
@State var conn_image: String = "network"
|
||||
@State var connecting: Bool = false
|
||||
|
||||
func update_connection_color() {
|
||||
func update_connection() {
|
||||
for relay in pool.relays {
|
||||
if relay.id == self.relay {
|
||||
let c = relay.connection
|
||||
if c.isConnected {
|
||||
conn_image = "network"
|
||||
conn_color = .green
|
||||
} else if c.isConnecting || c.isReconnecting {
|
||||
conn_color = .yellow
|
||||
connecting = true
|
||||
} else {
|
||||
conn_image = "exclamationmark.circle.fill"
|
||||
conn_color = .red
|
||||
}
|
||||
}
|
||||
@@ -31,15 +35,25 @@ struct RelayStatus: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Circle()
|
||||
.frame(width: 8.0, height: 8.0)
|
||||
.foregroundColor(conn_color)
|
||||
.onReceive(timer) { _ in
|
||||
update_connection_color()
|
||||
}
|
||||
.onAppear() {
|
||||
update_connection_color()
|
||||
HStack {
|
||||
if connecting {
|
||||
ProgressView()
|
||||
.padding(.trailing, 4)
|
||||
} else {
|
||||
Image(systemName: conn_image)
|
||||
.frame(width: 8.0, height: 8.0)
|
||||
.foregroundColor(conn_color)
|
||||
.padding(.leading, 5)
|
||||
.padding(.trailing, 10)
|
||||
}
|
||||
}
|
||||
.onReceive(timer) { _ in
|
||||
update_connection()
|
||||
}
|
||||
.onAppear() {
|
||||
update_connection()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,9 +11,12 @@ struct RelayType: View {
|
||||
let is_paid: Bool
|
||||
|
||||
var body: some View {
|
||||
|
||||
Image(systemName: is_paid ? "dollarsign.circle.fill" : "globe.americas.fill")
|
||||
.foregroundColor(is_paid ? Color("DamusGreen") : .gray)
|
||||
if is_paid {
|
||||
Image("bitcoin-logo")
|
||||
} else {
|
||||
Image(systemName: "globe.americas.fill")
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,32 +11,53 @@ struct RelayView: View {
|
||||
let state: DamusState
|
||||
let relay: String
|
||||
|
||||
@Binding var showActionButtons: Bool
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
HStack {
|
||||
RelayStatus(pool: state.pool, relay: relay)
|
||||
RelayType(is_paid: state.relay_metadata.lookup(relay_id: relay)?.is_paid ?? false)
|
||||
if let meta = state.relay_metadata.lookup(relay_id: relay) {
|
||||
NavigationLink {
|
||||
RelayDetailView(state: state, relay: relay, nip11: meta)
|
||||
} label: {
|
||||
Text(relay)
|
||||
if let privkey = state.keypair.privkey {
|
||||
if showActionButtons {
|
||||
RemoveButton(privkey: privkey, showText: false)
|
||||
}
|
||||
else {
|
||||
RelayStatus(pool: state.pool, relay: relay)
|
||||
}
|
||||
}
|
||||
|
||||
RelayType(is_paid: state.relay_metadata.lookup(relay_id: relay)?.is_paid ?? false)
|
||||
|
||||
if let meta = state.relay_metadata.lookup(relay_id: relay) {
|
||||
Text(relay)
|
||||
.background(
|
||||
NavigationLink("", destination: RelayDetailView(state: state, relay: relay, nip11: meta)).opacity(0.0)
|
||||
.disabled(showActionButtons)
|
||||
)
|
||||
Spacer()
|
||||
|
||||
Image(systemName: "info.circle")
|
||||
.font(.system(size: 20, weight: .regular))
|
||||
.foregroundColor(Color.accentColor)
|
||||
} else {
|
||||
Text(relay)
|
||||
Spacer()
|
||||
Image(systemName: "questionmark.circle")
|
||||
.font(.system(size: 20, weight: .regular))
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
}
|
||||
.swipeActions {
|
||||
if let privkey = state.keypair.privkey {
|
||||
RemoveAction(privkey: privkey)
|
||||
RemoveButton(privkey: privkey, showText: false)
|
||||
.tint(.red)
|
||||
}
|
||||
}
|
||||
.contextMenu {
|
||||
CopyAction(relay: relay)
|
||||
|
||||
if let privkey = state.keypair.privkey {
|
||||
RemoveAction(privkey: privkey)
|
||||
RemoveButton(privkey: privkey, showText: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,9 +69,9 @@ struct RelayView: View {
|
||||
Label(NSLocalizedString("Copy", comment: "Button to copy a relay server address."), systemImage: "doc.on.doc")
|
||||
}
|
||||
}
|
||||
|
||||
func RemoveAction(privkey: String) -> some View {
|
||||
Button {
|
||||
|
||||
func RemoveButton(privkey: String, showText: Bool) -> some View {
|
||||
Button(action: {
|
||||
guard let ev = state.contacts.event else {
|
||||
return
|
||||
}
|
||||
@@ -62,15 +83,20 @@ struct RelayView: View {
|
||||
|
||||
process_contact_event(state: state, ev: new_ev)
|
||||
state.pool.send(.event(new_ev))
|
||||
} label: {
|
||||
Label(NSLocalizedString("Delete", comment: "Button to delete a relay server that the user connects to."), systemImage: "trash")
|
||||
}) {
|
||||
if showText {
|
||||
Text(NSLocalizedString("Disconnect", comment: "Button to disconnect from a relay server."))
|
||||
}
|
||||
Image(systemName: "minus.circle.fill")
|
||||
.font(.system(size: 20, weight: .medium))
|
||||
.foregroundColor(.red)
|
||||
.padding(.leading, 5)
|
||||
}
|
||||
.tint(.red)
|
||||
}
|
||||
}
|
||||
|
||||
struct RelayView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
RelayView(state: test_damus_state(), relay: "wss://relay.damus.io")
|
||||
RelayView(state: test_damus_state(), relay: "wss://relay.damus.io", showActionButtons: .constant(false))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ struct ReplyView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.onAppear {
|
||||
references = gather_reply_ids(our_pubkey: damus.pubkey, from: replying_to)
|
||||
originalReferences = references
|
||||
|
||||
@@ -17,12 +17,14 @@ enum SearchState {
|
||||
enum SearchType {
|
||||
case event
|
||||
case profile
|
||||
case nip05
|
||||
}
|
||||
|
||||
struct SearchingEventView: View {
|
||||
let state: DamusState
|
||||
let evid: String
|
||||
let search_type: SearchType
|
||||
|
||||
@State var search_state: SearchState = .searching
|
||||
|
||||
var bech32_evid: String {
|
||||
@@ -35,6 +37,8 @@ struct SearchingEventView: View {
|
||||
|
||||
var search_name: String {
|
||||
switch search_type {
|
||||
case .nip05:
|
||||
return "nip05"
|
||||
case .profile:
|
||||
return "profile"
|
||||
case .event:
|
||||
@@ -42,6 +46,63 @@ struct SearchingEventView: View {
|
||||
}
|
||||
}
|
||||
|
||||
func handle_search(_ evid: String) {
|
||||
self.search_state = .searching
|
||||
|
||||
switch search_type {
|
||||
case .nip05:
|
||||
if let pk = state.profiles.nip05_pubkey[evid] {
|
||||
if state.profiles.lookup(id: pk) != nil {
|
||||
self.search_state = .found_profile(pk)
|
||||
}
|
||||
} else {
|
||||
Task.init {
|
||||
guard let nip05 = NIP05.parse(evid) else {
|
||||
self.search_state = .not_found
|
||||
return
|
||||
}
|
||||
guard let nip05_resp = await fetch_nip05(nip05: nip05) else {
|
||||
DispatchQueue.main.async {
|
||||
self.search_state = .not_found
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
guard let pk = nip05_resp.names[nip05.username] else {
|
||||
self.search_state = .not_found
|
||||
return
|
||||
}
|
||||
|
||||
self.search_state = .found_profile(pk)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case .event:
|
||||
if let ev = state.events.lookup(evid) {
|
||||
self.search_state = .found(ev)
|
||||
return
|
||||
}
|
||||
find_event(state: state, evid: evid, search_type: search_type, find_from: nil) { ev in
|
||||
if let ev {
|
||||
self.search_state = .found(ev)
|
||||
} else {
|
||||
self.search_state = .not_found
|
||||
}
|
||||
}
|
||||
case .profile:
|
||||
find_event(state: state, evid: evid, search_type: search_type, find_from: nil) { _ in
|
||||
if state.profiles.lookup(id: evid) != nil {
|
||||
self.search_state = .found_profile(evid)
|
||||
return
|
||||
} else {
|
||||
self.search_state = .not_found
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
switch search_state {
|
||||
@@ -67,31 +128,11 @@ struct SearchingEventView: View {
|
||||
Text("\(search_name.capitalized) not found", comment: "When a note or profile is not found when searching for it via its note id")
|
||||
}
|
||||
}
|
||||
.onChange(of: evid, debounceTime: 0.5) { evid in
|
||||
handle_search(evid)
|
||||
}
|
||||
.onAppear {
|
||||
|
||||
switch search_type {
|
||||
case .event:
|
||||
if let ev = state.events.lookup(evid) {
|
||||
self.search_state = .found(ev)
|
||||
return
|
||||
}
|
||||
find_event(state: state, evid: evid, search_type: search_type, find_from: nil) { ev in
|
||||
if let ev {
|
||||
self.search_state = .found(ev)
|
||||
} else {
|
||||
self.search_state = .not_found
|
||||
}
|
||||
}
|
||||
case .profile:
|
||||
find_event(state: state, evid: evid, search_type: search_type, find_from: nil) { _ in
|
||||
if state.profiles.lookup(id: evid) != nil {
|
||||
self.search_state = .found_profile(evid)
|
||||
return
|
||||
} else {
|
||||
self.search_state = .not_found
|
||||
}
|
||||
}
|
||||
}
|
||||
handle_search(evid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,15 @@
|
||||
|
||||
import SwiftUI
|
||||
import CryptoKit
|
||||
import NaturalLanguage
|
||||
|
||||
struct SearchHomeView: View {
|
||||
let damus_state: DamusState
|
||||
@StateObject var model: SearchHomeModel
|
||||
@State var search: String = ""
|
||||
@FocusState private var isFocused: Bool
|
||||
|
||||
let preferredLanguages = Set(Locale.preferredLanguages.map { localeToLanguage($0) })
|
||||
|
||||
var SearchInput: some View {
|
||||
HStack {
|
||||
@@ -30,7 +33,7 @@ struct SearchHomeView: View {
|
||||
|
||||
if(!search.isEmpty) {
|
||||
Text("Cancel", comment: "Cancel out of search view.")
|
||||
.foregroundColor(.accentColor)
|
||||
.foregroundStyle(LINEAR_GRADIENT)
|
||||
.padding(EdgeInsets(top: 0.0, leading: 0.0, bottom: 0.0, trailing: 10.0))
|
||||
.onTapGesture {
|
||||
self.search = ""
|
||||
@@ -41,12 +44,29 @@ struct SearchHomeView: View {
|
||||
}
|
||||
|
||||
var GlobalContent: some View {
|
||||
return TimelineView(events: model.events, loading: $model.loading, damus: damus_state, show_friend_icon: true, filter: { _ in true })
|
||||
.refreshable {
|
||||
// Fetch new information by unsubscribing and resubscribing to the relay
|
||||
model.unsubscribe()
|
||||
model.subscribe()
|
||||
return TimelineView(
|
||||
events: model.events,
|
||||
loading: $model.loading,
|
||||
damus: damus_state,
|
||||
show_friend_icon: true,
|
||||
filter: {
|
||||
if damus_state.settings.show_only_preferred_languages == false {
|
||||
return true
|
||||
}
|
||||
|
||||
// If we can't determine the note's language with 50%+ confidence, lean on the side of caution and show it anyway.
|
||||
guard let noteLanguage = $0.note_language(damus_state.keypair.privkey) else {
|
||||
return true
|
||||
}
|
||||
|
||||
return preferredLanguages.contains(noteLanguage)
|
||||
}
|
||||
)
|
||||
.refreshable {
|
||||
// Fetch new information by unsubscribing and resubscribing to the relay
|
||||
model.unsubscribe()
|
||||
model.subscribe()
|
||||
}
|
||||
}
|
||||
|
||||
var SearchContent: some View {
|
||||
|
||||
@@ -12,6 +12,7 @@ enum Search {
|
||||
case hashtag(String)
|
||||
case profile(String)
|
||||
case note(String)
|
||||
case nip05(String)
|
||||
case hex(String)
|
||||
}
|
||||
|
||||
@@ -41,6 +42,10 @@ struct SearchResultsView: View {
|
||||
NavigationLink(destination: dst) {
|
||||
Text("Search hashtag: #\(ht)", comment: "Navigation link to search hashtag.")
|
||||
}
|
||||
|
||||
case .nip05(let addr):
|
||||
SearchingEventView(state: damus_state, evid: addr, search_type: .nip05)
|
||||
|
||||
case .profile(let prof):
|
||||
let decoded = try? bech32_decode(prof)
|
||||
let hex = hex_encode(decoded!.data)
|
||||
@@ -95,6 +100,12 @@ func search_for_string(profiles: Profiles, _ new: String) -> Search? {
|
||||
return nil
|
||||
}
|
||||
|
||||
let splitted = new.split(separator: "@")
|
||||
|
||||
if splitted.count == 2 {
|
||||
return .nip05(new)
|
||||
}
|
||||
|
||||
if new.first! == "#" {
|
||||
let ht = String(new.dropFirst().filter{$0 != " "})
|
||||
return .hashtag(ht)
|
||||
|
||||
@@ -20,11 +20,11 @@ struct SideMenuView: View {
|
||||
let verticalSpacing: CGFloat = 20
|
||||
|
||||
func fillColor() -> Color {
|
||||
colorScheme == .light ? Color("DamusWhite") : Color("DamusBlack")
|
||||
colorScheme == .light ? DamusColors.white : DamusColors.black
|
||||
}
|
||||
|
||||
func textColor() -> Color {
|
||||
colorScheme == .light ? Color("DamusBlack") : Color("DamusWhite")
|
||||
colorScheme == .light ? DamusColors.black : DamusColors.white
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
@@ -32,7 +32,7 @@ struct SideMenuView: View {
|
||||
GeometryReader { _ in
|
||||
EmptyView()
|
||||
}
|
||||
.background(Color("DamusDarkGrey").opacity(0.6))
|
||||
.background(DamusColors.darkGrey.opacity(0.6))
|
||||
.opacity(isSidebarVisible ? 1 : 0)
|
||||
.animation(.default, value: isSidebarVisible)
|
||||
.onTapGesture {
|
||||
@@ -69,7 +69,7 @@ struct SideMenuView: View {
|
||||
}
|
||||
if let name = profile?.name {
|
||||
Text("@" + name)
|
||||
.foregroundColor(Color("DamusMediumGrey"))
|
||||
.foregroundColor(DamusColors.mediumGrey)
|
||||
.font(.body)
|
||||
.lineLimit(1)
|
||||
}
|
||||
|
||||
@@ -42,10 +42,10 @@ struct InnerTimelineView: View {
|
||||
nav_target = ev.inner_event ?? ev
|
||||
navigating = true
|
||||
}
|
||||
.padding(.top, 10)
|
||||
.padding(.top, 7)
|
||||
|
||||
Divider()
|
||||
.padding([.top], 10)
|
||||
.padding([.top], 7)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ struct UserRelaysView: View {
|
||||
let relays: [String]
|
||||
|
||||
@State var relay_state: [(String, Bool)]
|
||||
@State private var showAddButton = false
|
||||
|
||||
init (state: DamusState, pubkey: String, relays: [String]) {
|
||||
self.state = state
|
||||
@@ -30,12 +31,25 @@ struct UserRelaysView: View {
|
||||
|
||||
var body: some View {
|
||||
List(relay_state, id: \.0) { (r, add) in
|
||||
RecommendedRelayView(damus: state, relay: r, add_button: add)
|
||||
RecommendedRelayView(damus: state, relay: r, add_button: add, showActionButtons: $showAddButton)
|
||||
}
|
||||
.onReceive(handle_notify(.relays_changed)) { _ in
|
||||
self.relay_state = UserRelaysView.make_relay_state(pool: state.pool, relays: self.relays)
|
||||
}
|
||||
.navigationBarTitle("Relays")
|
||||
.toolbar{
|
||||
if state.keypair.privkey != nil {
|
||||
if showAddButton {
|
||||
Button("Done") {
|
||||
showAddButton.toggle()
|
||||
}
|
||||
} else {
|
||||
Button("Show Add") {
|
||||
showAddButton.toggle()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
@@ -233,7 +233,7 @@
|
||||
<key>one</key>
|
||||
<string>%2$@ و %1$d مستخدم آخر نشر منشورا تمت الإشارة لك فيه</string>
|
||||
<key>two</key>
|
||||
<string>%2$@ و %1$d مستخدمان آخران نشروا منشورا تمت الإشارة لك فيه</string>
|
||||
<string>%2$@ و %1$d آخران نشروا منشورا تمت الإشارة لك فيه</string>
|
||||
<key>few</key>
|
||||
<string>%2$@ و %1$d آخرون نشروا منشورا تمت الإشارة لك فيه</string>
|
||||
<key>many</key>
|
||||
@@ -257,7 +257,7 @@
|
||||
<key>one</key>
|
||||
<string>%2$@ و %1$d مستخدم آخر نشر منشورك</string>
|
||||
<key>two</key>
|
||||
<string>%2$@ و %1$d مستخدمان آخران نشروا منشورك</string>
|
||||
<string>%2$@ و %1$d آخران نشروا منشورك</string>
|
||||
<key>few</key>
|
||||
<string>%2$@ و %1$d آخرون نشروا منشورك</string>
|
||||
<key>many</key>
|
||||
@@ -353,7 +353,7 @@
|
||||
<key>one</key>
|
||||
<string>%2$@ و %1$d مستخدم آخر ومّض منشورا تمت الإشارة لك فيه</string>
|
||||
<key>two</key>
|
||||
<string>%2$@ و %1$d مستخدمان آخران ومّضوا منشورا تمت الإشارة لك فيه</string>
|
||||
<string>%2$@ و %1$d آخران ومّضوا منشورا تمت الإشارة لك فيه</string>
|
||||
<key>few</key>
|
||||
<string>%2$@ و %1$d آخررن ومّضوا منشورا تمت الإشارة لك فيه</string>
|
||||
<key>many</key>
|
||||
@@ -377,7 +377,7 @@
|
||||
<key>one</key>
|
||||
<string>%2$@ و %1$d مستخدم آخر ومّض منشورك</string>
|
||||
<key>two</key>
|
||||
<string>%2$@ و %1$d مستخدمان آخران ومّضوا منشورك</string>
|
||||
<string>%2$@ و %1$d آخران ومّضوا منشورك</string>
|
||||
<key>few</key>
|
||||
<string>%2$@ و %1$d آخرون ومّضوا منشورك</string>
|
||||
<key>many</key>
|
||||
@@ -401,7 +401,7 @@
|
||||
<key>one</key>
|
||||
<string>%2$@ و %1$d مستخدم آخر ومّض حسابك</string>
|
||||
<key>two</key>
|
||||
<string>%2$@ و %1$d مستخدمان آخران ومّضوا حسابك</string>
|
||||
<string>%2$@ و %1$d آخران ومّضوا حسابك</string>
|
||||
<key>few</key>
|
||||
<string>%2$@ و %1$d آخرون ومّضوا حسابك</string>
|
||||
<key>many</key>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -9,6 +9,7 @@ import SwiftUI
|
||||
|
||||
@main
|
||||
struct damusApp: App {
|
||||
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
MainView()
|
||||
@@ -43,6 +44,19 @@ struct MainView: View {
|
||||
}
|
||||
}
|
||||
|
||||
class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate {
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
|
||||
UNUserNotificationCenter.current().delegate = self
|
||||
return true
|
||||
}
|
||||
|
||||
// Handle the notification in the foreground state
|
||||
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
|
||||
// Display the notification in the foreground
|
||||
completionHandler([.banner, .list, .sound, .badge])
|
||||
}
|
||||
}
|
||||
|
||||
func needs_setup() -> Keypair? {
|
||||
return get_saved_keypair()
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -136,7 +136,8 @@ Sentence composed of 2 variables to describe how many people are following a use
|
||||
<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>
|
||||
<note>Button text to add bookmark to a note.
|
||||
Context menu option for adding a note bookmark.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Add Relay" xml:space="preserve">
|
||||
<source>Add Relay</source>
|
||||
@@ -911,7 +912,8 @@ Picker option to indicate that a zap should be sent privately and not identify t
|
||||
<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>
|
||||
<note>Button text to remove bookmark from a note.
|
||||
Context menu option for removing a note bookmark.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Remove all" xml:space="preserve">
|
||||
<source>Remove all</source>
|
||||
@@ -1175,14 +1177,14 @@ Picker option to indicate that a zap should be sent privately and not identify t
|
||||
<target>Translate Note</target>
|
||||
<note>Button to translate note from different language.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Translated from (lang)" xml:space="preserve">
|
||||
<source>Translated from (lang)</source>
|
||||
<target>Translated from (lang)</target>
|
||||
<trans-unit id="Translated from %@" xml:space="preserve">
|
||||
<source>Translated from %@</source>
|
||||
<target>Translated from %@</target>
|
||||
<note>Button to indicate that the note has been translated from a different language.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Translating from (lang)..." xml:space="preserve">
|
||||
<source>Translating from (lang)...</source>
|
||||
<target>Translating from (lang)...</target>
|
||||
<trans-unit id="Translating from %@..." xml:space="preserve">
|
||||
<source>Translating from %@...</source>
|
||||
<target>Translating from %@...</target>
|
||||
<note>Button to indicate that the note is in the process of being translated from a different language.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Translations" xml:space="preserve">
|
||||
@@ -1190,10 +1192,10 @@ Picker option to indicate that a zap should be sent privately and not identify t
|
||||
<target>Translations</target>
|
||||
<note>Section title for selecting the translation service.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Type DELETE to delete" xml:space="preserve">
|
||||
<source>Type DELETE to delete</source>
|
||||
<target>Type DELETE to delete</target>
|
||||
<note>Text field prompt asking user to type the word DELETE to confirm that they want to proceed with deleting their account. The all caps lock DELETE word should not be translated. Everything else should.</note>
|
||||
<trans-unit id="Type %@ to delete" xml:space="preserve">
|
||||
<source>Type %@ to delete</source>
|
||||
<target>Type %@ to delete</target>
|
||||
<note>Text field prompt asking user to type DELETE in all caps to confirm that they want to proceed with deleting their account.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Type your post here..." xml:space="preserve">
|
||||
<source>Type your post here...</source>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
BIN
damus/es-ES.lproj/InfoPlist.strings
Normal file
BIN
damus/es-ES.lproj/InfoPlist.strings
Normal file
Binary file not shown.
BIN
damus/es-ES.lproj/Localizable.strings
Normal file
BIN
damus/es-ES.lproj/Localizable.strings
Normal file
Binary file not shown.
330
damus/es-ES.lproj/Localizable.stringsdict
Normal file
330
damus/es-ES.lproj/Localizable.stringsdict
Normal file
@@ -0,0 +1,330 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<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 otra nota ...</string>
|
||||
<key>many</key>
|
||||
<string>... %d otras notas ...</string>
|
||||
<key>other</key>
|
||||
<string>... %d otras notas ...</string>
|
||||
</dict>
|
||||
</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>many</key>
|
||||
<string>Seguidores</string>
|
||||
<key>other</key>
|
||||
<string>Seguidores</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>following_count</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@FOLLOWING@</string>
|
||||
<key>FOLLOWING</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>Siguiendo</string>
|
||||
<key>many</key>
|
||||
<string>Siguiendo</string>
|
||||
<key>other</key>
|
||||
<string>Siguiendo</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$@ y %1$d más han reaccionado a un post en el que te etiquetaron</string>
|
||||
<key>many</key>
|
||||
<string>%2$@ y %1$d más han reaccionado a un post en el que te etiquetaron</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ y %1$d más han reaccionado a un post en el que te etiquetaron</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$@ y %1$d más han reaccionado a tu post</string>
|
||||
<key>many</key>
|
||||
<string>%2$@ y %1$d más han reaccionado a tu post</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ y %1$d más han reaccionado a tu 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$@ y %1$d más han reaccionado a tu perfil</string>
|
||||
<key>many</key>
|
||||
<string>%2$@ y %1$d más han reaccionado a tu perfil</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ y %1$d más han reaccionado a tu perfil</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reactions_count</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@REACTIONS@</string>
|
||||
<key>REACTIONS</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>Reacción</string>
|
||||
<key>many</key>
|
||||
<string>Reacciones</string>
|
||||
<key>other</key>
|
||||
<string>Reacciones</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>relays_count</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@RELAYS@</string>
|
||||
<key>RELAYS</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>Relay</string>
|
||||
<key>many</key>
|
||||
<string>Relays</string>
|
||||
<key>other</key>
|
||||
<string>Relays</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>replying_to_two_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>Respondiendo a %2$@, %3$@ & %1$d más</string>
|
||||
<key>many</key>
|
||||
<string>Respondiendo a %2$@, %3$@ & %1$d más</string>
|
||||
<key>other</key>
|
||||
<string>Respondiendo a %2$@, %3$@ & %1$d más</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$@ y %1$d más han republicado un post en el que te etiquetaron</string>
|
||||
<key>many</key>
|
||||
<string>%2$@ y %1$d más han republicado un post en el que te etiquetaron</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ y %1$d más han republicado un post en el que te etiquetaron</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$@ y %1$d más han republicado tu post</string>
|
||||
<key>many</key>
|
||||
<string>%2$@ y %1$d más han republicado tu post</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ y %1$d más han republicado tu 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$@ y %1$d más han republicado tu perfil</string>
|
||||
<key>many</key>
|
||||
<string>%2$@ y %1$d más han republicado tu perfil</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ y %1$d más han republicado tu perfil</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reposts_count</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@REPOSTS@</string>
|
||||
<key>REPOSTS</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>Republicación</string>
|
||||
<key>many</key>
|
||||
<string>Republicaciones</string>
|
||||
<key>other</key>
|
||||
<string>Republicaciones</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>sats_count</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%1$#@SATS@</string>
|
||||
<key>SATS</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>@</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ sat</string>
|
||||
<key>many</key>
|
||||
<string>%2$@ sats</string>
|
||||
<key>other</key>
|
||||
<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$@ y %1$d más han zapeado un post en el que te etiquetaron</string>
|
||||
<key>many</key>
|
||||
<string>%2$@ y %1$d más han zapeado un post en el que te etiquetaron</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ y %1$d más han zapeado un post en el que te etiquetaron</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$@ y %1$d más han zapeado tu post</string>
|
||||
<key>many</key>
|
||||
<string>%2$@ y %1$d más han zapeado tu post</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ y %1$d más han zapeado tu 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$@ y %1$d más han zapeado tu perfil</string>
|
||||
<key>many</key>
|
||||
<string>%2$@ y %1$d más han zapeado tu perfil</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ y %1$d más han zapeado tu perfil</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zaps_count</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@ZAPS@</string>
|
||||
<key>ZAPS</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>Zap</string>
|
||||
<key>many</key>
|
||||
<string>Zaps</string>
|
||||
<key>other</key>
|
||||
<string>Zaps</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -15,11 +15,11 @@
|
||||
<key>one</key>
|
||||
<string>... %d inna notatka ...</string>
|
||||
<key>few</key>
|
||||
<string>... %d innych notatek ...</string>
|
||||
<string>... %d inne notatki ...</string>
|
||||
<key>many</key>
|
||||
<string>... %d innych notatek ...</string>
|
||||
<key>other</key>
|
||||
<string>... %d inne notatki ...</string>
|
||||
<string>... %d innych notatek ...</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>followers_count</key>
|
||||
@@ -35,11 +35,11 @@
|
||||
<key>one</key>
|
||||
<string>Obserwujący</string>
|
||||
<key>few</key>
|
||||
<string>Followers</string>
|
||||
<string>Obserwujących</string>
|
||||
<key>many</key>
|
||||
<string>Followers</string>
|
||||
<string>Obserwujących</string>
|
||||
<key>other</key>
|
||||
<string>Obserwujący</string>
|
||||
<string>Obserwujących</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>following_count</key>
|
||||
@@ -53,13 +53,13 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>Obserwujący</string>
|
||||
<string>Obserwuje</string>
|
||||
<key>few</key>
|
||||
<string>Obserwujących</string>
|
||||
<string>Obserwuje</string>
|
||||
<key>many</key>
|
||||
<string>Obserwujących</string>
|
||||
<string>Obserwuje</string>
|
||||
<key>other</key>
|
||||
<string>Obserwujących</string>
|
||||
<string>Obserwuje</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reacted_tagged_in_3</key>
|
||||
@@ -135,11 +135,11 @@
|
||||
<key>one</key>
|
||||
<string>Reakcja</string>
|
||||
<key>few</key>
|
||||
<string>Reactions</string>
|
||||
<key>many</key>
|
||||
<string>Reactions</string>
|
||||
<key>other</key>
|
||||
<string>Reakcje</string>
|
||||
<key>many</key>
|
||||
<string>Reakcji</string>
|
||||
<key>other</key>
|
||||
<string>Reakcji</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>relays_count</key>
|
||||
@@ -159,7 +159,7 @@
|
||||
<key>many</key>
|
||||
<string>Przekaźników</string>
|
||||
<key>other</key>
|
||||
<string>Przekaźniki</string>
|
||||
<string>Przekaźników</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>replying_to_two_and_others</key>
|
||||
@@ -173,13 +173,13 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>Replying to %2$@, %3$@ i %1$d innej osoba</string>
|
||||
<string>W odpowiedzi do %2$@, %3$@ i %1$d innej osobie</string>
|
||||
<key>few</key>
|
||||
<string>Replying to %2$@, %3$@ i %1$d others</string>
|
||||
<string>W odpowiedzi do %2$@, %3$@ i %1$d innych osób</string>
|
||||
<key>many</key>
|
||||
<string>Replying to %2$@, %3$@ i %1$d others</string>
|
||||
<string>W odpowiedzi do %2$@, %3$@ i %1$d innych osób</string>
|
||||
<key>other</key>
|
||||
<string>Replying to %2$@, %3$@ i %1$d innym osobom</string>
|
||||
<string>W odpowiedzi do %2$@, %3$@ i %1$d innych osób</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reposted_tagged_in_3</key>
|
||||
@@ -195,7 +195,7 @@
|
||||
<key>one</key>
|
||||
<string>%2$@ i %1$d inna osoba opublikowała wiadomość, w której jesteś oznaczony</string>
|
||||
<key>few</key>
|
||||
<string>%2$@ i %1$d inne osobe opublikowały wiadomość, w której jesteś oznaczony</string>
|
||||
<string>%2$@ i %1$d inne osoby opublikowały wiadomość, w której jesteś oznaczony</string>
|
||||
<key>many</key>
|
||||
<string>%2$@ i %1$d innych osób opublikowało wiadomość, w której jesteś oznaczony</string>
|
||||
<key>other</key>
|
||||
@@ -255,11 +255,11 @@
|
||||
<key>one</key>
|
||||
<string>Podany dalej</string>
|
||||
<key>few</key>
|
||||
<string>Reposts</string>
|
||||
<key>many</key>
|
||||
<string>Reposts</string>
|
||||
<key>other</key>
|
||||
<string>Podane dalej</string>
|
||||
<key>many</key>
|
||||
<string>Podanych dalej</string>
|
||||
<key>other</key>
|
||||
<string>Podanych dalej</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>sats_count</key>
|
||||
@@ -275,11 +275,11 @@
|
||||
<key>one</key>
|
||||
<string>%2$@ sat</string>
|
||||
<key>few</key>
|
||||
<string>%2$@ sats</string>
|
||||
<string>%2$@ satsy</string>
|
||||
<key>many</key>
|
||||
<string>%2$@ sats</string>
|
||||
<string>%2$@ satsów</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ satoszy</string>
|
||||
<string>%2$@ satsa</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zapped_tagged_in_3</key>
|
||||
@@ -359,7 +359,7 @@
|
||||
<key>many</key>
|
||||
<string>Zapów</string>
|
||||
<key>other</key>
|
||||
<string>Zapy</string>
|
||||
<string>Zapa</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
|
||||
BIN
damus/pt-BR.lproj/InfoPlist.strings
Normal file
BIN
damus/pt-BR.lproj/InfoPlist.strings
Normal file
Binary file not shown.
BIN
damus/pt-BR.lproj/Localizable.strings
Normal file
BIN
damus/pt-BR.lproj/Localizable.strings
Normal file
Binary file not shown.
330
damus/pt-BR.lproj/Localizable.stringsdict
Normal file
330
damus/pt-BR.lproj/Localizable.stringsdict
Normal file
@@ -0,0 +1,330 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<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 Outras observações ... </string>
|
||||
<key>many</key>
|
||||
<string>... %d Outras observações ... </string>
|
||||
<key>other</key>
|
||||
<string>... %d outras observações ... </string>
|
||||
</dict>
|
||||
</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>many</key>
|
||||
<string>Seguidores</string>
|
||||
<key>other</key>
|
||||
<string>Seguidores</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>following_count</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@FOLLOWING@</string>
|
||||
<key>FOLLOWING</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>Seguindo</string>
|
||||
<key>many</key>
|
||||
<string>Seguindo</string>
|
||||
<key>other</key>
|
||||
<string>Seguindo</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$@ e %1$d outro reagiram a uma publicação na qual você está marcado</string>
|
||||
<key>many</key>
|
||||
<string>%2$@ e %1$d muitos outros reagiram a uma publicação na qual você está marcado</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ e %1$d outros reagiram a uma publicação na qual você está marcado</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$@ e %1$d outro reagiu à sua publicação</string>
|
||||
<key>many</key>
|
||||
<string>%2$@ e %1$d muitos outros reagiram à sua publicação</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ e %1$d outros reagiram à sua publicação</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$@ e %1$d outros reagiram ao seu perfil</string>
|
||||
<key>many</key>
|
||||
<string>%2$@ e %1$d muitos outros reagiram ao seu perfil</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ e %1$d outros reagiram ao seu perfil</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reactions_count</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@REACTIONS@</string>
|
||||
<key>REACTIONS</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>Reação</string>
|
||||
<key>many</key>
|
||||
<string>Reações</string>
|
||||
<key>other</key>
|
||||
<string>Reações</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>relays_count</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@RELAYS@</string>
|
||||
<key>RELAYS</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>Transmissão </string>
|
||||
<key>many</key>
|
||||
<string>Transmissões</string>
|
||||
<key>other</key>
|
||||
<string>Transmissões</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>replying_to_two_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>Respondendo para %2$@, %3$@ %1$d e outro</string>
|
||||
<key>many</key>
|
||||
<string>Respondendo para %2$@, %3$@ %1$d e muitos outros</string>
|
||||
<key>other</key>
|
||||
<string>Respondendo para %2$@, %3$@ %1$d e outros</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$@ e %1$d outro republicaram uma publicação na qual você está marcado</string>
|
||||
<key>many</key>
|
||||
<string>%2$@ e %1$d muitos outros republicaram uma publicação na qual você está marcado</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ e %1$d outros republicaram uma publicação na qual você está marcado</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$@ e %1$d outro republicaram seu perfil</string>
|
||||
<key>many</key>
|
||||
<string>%2$@ e %1$d muitos outros republicaram seu perfil</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ e %1$d outros republicaram seu perfil</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$@ e %1$d outro republicaram seu perfil</string>
|
||||
<key>many</key>
|
||||
<string>%2$@ e %1$d muitos outros republicaram seu perfil</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ e %1$d outros republicaram seu perfil</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reposts_count</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@REPOSTS@</string>
|
||||
<key>REPOSTS</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>Republicação</string>
|
||||
<key>many</key>
|
||||
<string>Republicações</string>
|
||||
<key>other</key>
|
||||
<string>Republicações</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>sats_count</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%1$#@SATS@</string>
|
||||
<key>SATS</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>@</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ sat</string>
|
||||
<key>many</key>
|
||||
<string>%2$@ sats</string>
|
||||
<key>other</key>
|
||||
<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$@ e %1$d outro eletrizaram uma publicação na qual você está marcado</string>
|
||||
<key>many</key>
|
||||
<string>%2$@ e %1$d muitos outros eletrizaram uma publicação na qual você está marcado</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ e %1$d outros eletrizaram uma publicação na qual você está marcado</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$@ e %1$d outro eletrizaram sua publicação</string>
|
||||
<key>many</key>
|
||||
<string>%2$@ e %1$d muitos outros eletrizaram sua publicação</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ e %1$d outros eletrizaram sua publicação</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$@ e %1$d outro eletrizaram seu perfil</string>
|
||||
<key>many</key>
|
||||
<string>%2$@ e %1$d muitos outros eletrizaram seu perfil</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ e %1$d outros eletrizaram seu perfil</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zaps_count</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@ZAPS@</string>
|
||||
<key>ZAPS</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>Iluminou</string>
|
||||
<key>many</key>
|
||||
<string>Muitos iluminaram</string>
|
||||
<key>other</key>
|
||||
<string>Iluminaram</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
Binary file not shown.
Binary file not shown.
@@ -39,7 +39,27 @@
|
||||
<key>many</key>
|
||||
<string>Подписчиков</string>
|
||||
<key>other</key>
|
||||
<string>Подписчик</string>
|
||||
<string>Подписчики</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>following_count</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@FOLLOWING@</string>
|
||||
<key>FOLLOWING</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>Подписки</string>
|
||||
<key>few</key>
|
||||
<string>Подписки</string>
|
||||
<key>many</key>
|
||||
<string>Подписки</string>
|
||||
<key>other</key>
|
||||
<string>Подписки</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reacted_tagged_in_3</key>
|
||||
@@ -119,7 +139,7 @@
|
||||
<key>many</key>
|
||||
<string>Реакций</string>
|
||||
<key>other</key>
|
||||
<string>Реакция</string>
|
||||
<string>Реакции</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>relays_count</key>
|
||||
@@ -133,13 +153,13 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>Релей</string>
|
||||
<string>Реле</string>
|
||||
<key>few</key>
|
||||
<string>Релея</string>
|
||||
<string>Реле</string>
|
||||
<key>many</key>
|
||||
<string>Релеев</string>
|
||||
<string>Реле</string>
|
||||
<key>other</key>
|
||||
<string>Релей</string>
|
||||
<string>Реле</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>replying_to_two_and_others</key>
|
||||
@@ -193,13 +213,13 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ и %1$d сделали репост Вашей заметки</string>
|
||||
<string>%2$@ и %1$d сделали репост Вашего поста</string>
|
||||
<key>few</key>
|
||||
<string>%2$@ и еще %1$d пользователя сделали репост Вашей заметки</string>
|
||||
<string>%2$@ и еще %1$d пользователя сделали репост Вашего поста</string>
|
||||
<key>many</key>
|
||||
<string>%2$@ и еще %1$d пользователей сделали репост Вашей заметки</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ и еще %1$d пользователь сделали репост Вашей заметки</string>
|
||||
<string>%2$@ и еще %1$d пользователь сделали репост Вашего поста</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reposted_your_profile_3</key>
|
||||
@@ -235,9 +255,9 @@
|
||||
<key>one</key>
|
||||
<string>Репост</string>
|
||||
<key>few</key>
|
||||
<string>Reposts</string>
|
||||
<string>Репостов</string>
|
||||
<key>many</key>
|
||||
<string>Reposts</string>
|
||||
<string>Репостов</string>
|
||||
<key>other</key>
|
||||
<string>Репосты</string>
|
||||
</dict>
|
||||
|
||||
Binary file not shown.
@@ -205,7 +205,7 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>one</key>
|
||||
<string>Dela</string>
|
||||
<string>Delat</string>
|
||||
<key>other</key>
|
||||
<string>Delningar</string>
|
||||
</dict>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
BIN
damus/vi.lproj/InfoPlist.strings
Normal file
BIN
damus/vi.lproj/InfoPlist.strings
Normal file
Binary file not shown.
BIN
damus/vi.lproj/Localizable.strings
Normal file
BIN
damus/vi.lproj/Localizable.strings
Normal file
Binary file not shown.
258
damus/vi.lproj/Localizable.stringsdict
Normal file
258
damus/vi.lproj/Localizable.stringsdict
Normal file
@@ -0,0 +1,258 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<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>other</key>
|
||||
<string>... %d bài đăng khác ...</string>
|
||||
</dict>
|
||||
</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>other</key>
|
||||
<string>Người theo dõi</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>following_count</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@FOLLOWING@</string>
|
||||
<key>FOLLOWING</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>other</key>
|
||||
<string>Đang theo dõi</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>other</key>
|
||||
<string>%2$@ và %1$d người khác đã bày tỏ cảm xúc về bài đăng mà bạn được gắn thẻ</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>other</key>
|
||||
<string>%2$@ và %1$d người khác đã bày tỏ cảm xúc về bài đăng bạn</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>other</key>
|
||||
<string>%2$@ và %1$d người khác đã bày tỏ cảm xúc về hồ sơ của bạn</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reactions_count</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@REACTIONS@</string>
|
||||
<key>REACTIONS</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>other</key>
|
||||
<string>Cảm xúc</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>relays_count</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@RELAYS@</string>
|
||||
<key>RELAYS</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>other</key>
|
||||
<string>Relays</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>replying_to_two_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>other</key>
|
||||
<string>Trả lời %2$@, %3$@ & %1$d người khác</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>other</key>
|
||||
<string>%2$@ và %1$d người khác đã đăng lại bài đăng mà bạn được gắn thẻ</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>other</key>
|
||||
<string>%2$@ và %1$d người khác đã đăng lại bài đăng của bạn</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>other</key>
|
||||
<string>%2$@ và %1$d người khác đã đăng lại hồ sơ của bạn</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reposts_count</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@REPOSTS@</string>
|
||||
<key>REPOSTS</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>other</key>
|
||||
<string>Đăng lại</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>sats_count</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%1$#@SATS@</string>
|
||||
<key>SATS</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>@</string>
|
||||
<key>other</key>
|
||||
<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>other</key>
|
||||
<string>%2$@ và %1$d người khác đã zap bài đăng mà bạn được gắn thẻ</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>other</key>
|
||||
<string>%2$@ và %1$d người khác đã zap bài đăng của bạn</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>other</key>
|
||||
<string>%2$@ và %1$d người khác đã zap hồ sơ của bạn</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zaps_count</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@ZAPS@</string>
|
||||
<key>ZAPS</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>other</key>
|
||||
<string>Zaps</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
Binary file not shown.
@@ -13,7 +13,7 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>other</key>
|
||||
<string>... %d 条更多推 ...</string>
|
||||
<string>... %d 条更多便条...</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>followers_count</key>
|
||||
@@ -55,7 +55,7 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ 和 %1$d 个其他用户回应了提到你的推文</string>
|
||||
<string>%2$@ 和 %1$d 个其他用户回应了提到你的便条</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reacted_your_post_3</key>
|
||||
@@ -69,7 +69,7 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ 和 %1$d 个其他用户回应了你的推文</string>
|
||||
<string>%2$@ 和 %1$d 个其他用户回应了你的便条</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reacted_your_profile_3</key>
|
||||
@@ -139,7 +139,7 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ 和 %1$d 个其他用户转发了提到你的推文</string>
|
||||
<string>%2$@ 和 %1$d 个其他用户转发了提到你的便条</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reposted_your_post_3</key>
|
||||
@@ -153,7 +153,7 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ 和 %1$d 个其他用户转发了你的推文</string>
|
||||
<string>%2$@ 和 %1$d 个其他用户转发了你的便条</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>reposted_your_profile_3</key>
|
||||
@@ -209,7 +209,7 @@
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ 和 %1$d 个其他用户电击了提到你的推文</string>
|
||||
<string>%2$@ 和 %1$d 个其他用户电击了提到你的便条</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>zapped_your_post_3</key>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user