Compare commits
42 Commits
tyiu/selec
...
tyiu/fix-z
| Author | SHA1 | Date | |
|---|---|---|---|
|
69ea3ed385
|
|||
|
|
0bdec912f8 | ||
|
|
6d8312fa57 | ||
|
|
f6d56179eb | ||
|
|
193e922c9c | ||
|
|
a1a89dc98e | ||
|
|
3e764e75e4 | ||
|
|
7c563cb0ae | ||
|
a328b0d1a8
|
|||
|
|
5018b9aa1e | ||
|
|
1f6657e471 | ||
|
|
062b5dc040 | ||
|
|
390c9162ae | ||
|
|
94f66adf8d | ||
|
|
d547dade04 | ||
|
|
94a67adff9 | ||
|
|
29f192c377 | ||
|
4e67c88607
|
|||
|
|
42200c347b | ||
|
36f05ccaed
|
|||
|
98a1b95d12
|
|||
|
|
4cdef502e9 | ||
|
|
ae2e70ba7d | ||
|
|
1b4e54582f | ||
|
|
909148f0be | ||
|
|
c100c6db47 | ||
|
|
8d3fb397f7 | ||
|
|
f8742a609c | ||
|
|
d55d0d61ed | ||
|
|
cf90480501 | ||
|
|
f0075904c2 | ||
|
a41acc12e7
|
|||
|
|
1e22984d52 | ||
| 9080e4efae | |||
|
6488634eda
|
|||
|
355cd1283c
|
|||
|
|
6ed9c408f9 | ||
|
|
5f52e6f62f | ||
|
|
2366089896 | ||
|
|
9a95967a81 | ||
|
|
504108da75 | ||
|
|
d43a2ff92d |
19
CHANGELOG.md
@@ -1,3 +1,21 @@
|
||||
|
||||
## [1.1.0-2] - 2023-02-14
|
||||
|
||||
### Added
|
||||
|
||||
- Save drafts to posts, replies and DMs (Terry Yiu)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Ensure stats get updated in realtime on action bars (William Casarin)
|
||||
- Fix reposts not getting counted properly (William Casarin)
|
||||
- Fix a bug where zaps on other people's posts weren't showing (William Casarin)
|
||||
- Fix punctuation getting included in some urls (Gert Goet)
|
||||
- Improve language detection (Terry Yiu)
|
||||
- Fix some animated image crashes (William Casarin)
|
||||
|
||||
|
||||
[1.1.0-2]: https://github.com/damus-io/damus/releases/tag/v1.1.0-2
|
||||
## [1.0.0-15] - 2023-02-10
|
||||
|
||||
### Added
|
||||
@@ -559,3 +577,4 @@
|
||||
|
||||
[0.1.2]: https://github.com/damus-io/damus/releases/tag/v0.1.2
|
||||
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
4C285C8A2838B985008A31F1 /* ProfilePictureSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C285C892838B985008A31F1 /* ProfilePictureSelector.swift */; };
|
||||
4C285C8C28398BC7008A31F1 /* Keys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C285C8B28398BC6008A31F1 /* Keys.swift */; };
|
||||
4C285C8E28399BFE008A31F1 /* SaveKeysView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C285C8D28399BFD008A31F1 /* SaveKeysView.swift */; };
|
||||
4C2CDDF7299D4A5E00879FD5 /* Debouncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2CDDF6299D4A5E00879FD5 /* Debouncer.swift */; };
|
||||
4C363A8428233689006E126D /* Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A8328233689006E126D /* Parser.swift */; };
|
||||
4C363A8828236948006E126D /* BlocksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A8728236948006E126D /* BlocksView.swift */; };
|
||||
4C363A8A28236B57006E126D /* MentionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A8928236B57006E126D /* MentionView.swift */; };
|
||||
@@ -205,9 +206,10 @@
|
||||
647D9A8D2968520300A295DE /* SideMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 647D9A8C2968520300A295DE /* SideMenuView.swift */; };
|
||||
64FBD06F296255C400D9D3B2 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64FBD06E296255C400D9D3B2 /* Theme.swift */; };
|
||||
6C7DE41F2955169800E66263 /* Vault in Frameworks */ = {isa = PBXBuildFile; productRef = 6C7DE41E2955169800E66263 /* Vault */; };
|
||||
7C45AE71297353390031D7BC /* KFImageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C45AE70297353390031D7BC /* KFImageModel.swift */; };
|
||||
7C60CAEF298471A1009C80D6 /* CoreSVG.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C60CAEE298471A1009C80D6 /* CoreSVG.swift */; };
|
||||
7C902AE32981D55B002AB16E /* ZoomableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C902AE22981D55B002AB16E /* ZoomableScrollView.swift */; };
|
||||
7C95CAEE299DCEF1009DCB67 /* KFOptionSetter+.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C95CAED299DCEF1009DCB67 /* KFOptionSetter+.swift */; };
|
||||
7CFF6317299FEFE5005D382A /* SelectableText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CFF6316299FEFE5005D382A /* SelectableText.swift */; };
|
||||
9609F058296E220800069BF3 /* BannerImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9609F057296E220800069BF3 /* BannerImageView.swift */; };
|
||||
BA693074295D649800ADDB87 /* UserSettingsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA693073295D649800ADDB87 /* UserSettingsStore.swift */; };
|
||||
BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */; };
|
||||
@@ -249,6 +251,9 @@
|
||||
3A25EF142992DA5D008ABE69 /* el-GR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "el-GR"; path = "el-GR.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
3A25EF152992DA5D008ABE69 /* el-GR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "el-GR"; path = "el-GR.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
||||
3A2B8B0A296A8982009CC16D /* en-US */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "en-US"; path = "en-US.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
3A4F3320297CCFEE004B5F72 /* fr-FR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "fr-FR"; path = "fr-FR.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
||||
3A4F3321297CCFEE004B5F72 /* fr-FR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "fr-FR"; path = "fr-FR.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
3A4F3322297CCFEE004B5F72 /* fr-FR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "fr-FR"; path = "fr-FR.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
||||
@@ -259,6 +264,12 @@
|
||||
3A66D927299472FA008B44F4 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
3A66D928299472FA008B44F4 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
3A66D929299472FA008B44F4 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ja; path = ja.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
3A827A18299FC69D00C4D171 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
3A827A19299FC69D00C4D171 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
3A827A1A299FC69D00C4D171 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = ru; path = ru.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
3A8624D9299E82BE00BD8BE9 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||
3A8624DA299E82BE00BD8BE9 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
3A8624DB299E82BE00BD8BE9 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = cs; path = cs.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
|
||||
3A929C20297F2CF80090925E /* it-IT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "it-IT"; path = "it-IT.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
||||
3A929C21297F2CF80090925E /* it-IT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "it-IT"; path = "it-IT.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
3A929C22297F2CF80090925E /* it-IT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "it-IT"; path = "it-IT.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
||||
@@ -312,6 +323,7 @@
|
||||
4C285C892838B985008A31F1 /* ProfilePictureSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfilePictureSelector.swift; sourceTree = "<group>"; };
|
||||
4C285C8B28398BC6008A31F1 /* Keys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keys.swift; sourceTree = "<group>"; };
|
||||
4C285C8D28399BFD008A31F1 /* SaveKeysView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveKeysView.swift; sourceTree = "<group>"; };
|
||||
4C2CDDF6299D4A5E00879FD5 /* Debouncer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Debouncer.swift; sourceTree = "<group>"; };
|
||||
4C363A8328233689006E126D /* Parser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Parser.swift; sourceTree = "<group>"; };
|
||||
4C363A8728236948006E126D /* BlocksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlocksView.swift; sourceTree = "<group>"; };
|
||||
4C363A8928236B57006E126D /* MentionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MentionView.swift; sourceTree = "<group>"; };
|
||||
@@ -507,9 +519,10 @@
|
||||
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>"; };
|
||||
7C45AE70297353390031D7BC /* KFImageModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KFImageModel.swift; sourceTree = "<group>"; };
|
||||
7C60CAEE298471A1009C80D6 /* CoreSVG.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreSVG.swift; sourceTree = "<group>"; };
|
||||
7C902AE22981D55B002AB16E /* ZoomableScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZoomableScrollView.swift; sourceTree = "<group>"; };
|
||||
7C95CAED299DCEF1009DCB67 /* KFOptionSetter+.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KFOptionSetter+.swift"; sourceTree = "<group>"; };
|
||||
7CFF6316299FEFE5005D382A /* SelectableText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectableText.swift; sourceTree = "<group>"; };
|
||||
9609F057296E220800069BF3 /* BannerImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BannerImageView.swift; sourceTree = "<group>"; };
|
||||
BA693073295D649800ADDB87 /* UserSettingsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettingsStore.swift; sourceTree = "<group>"; };
|
||||
BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectWalletView.swift; sourceTree = "<group>"; };
|
||||
@@ -655,7 +668,6 @@
|
||||
BA693073295D649800ADDB87 /* UserSettingsStore.swift */,
|
||||
4FE60CDC295E1C5E00105A1F /* Wallet.swift */,
|
||||
4CB88392296F798300DC99E7 /* ReactionsModel.swift */,
|
||||
7C45AE70297353390031D7BC /* KFImageModel.swift */,
|
||||
4CF0ABD32980996B00D66079 /* Report.swift */,
|
||||
4CF0ABDD2981A69500D66079 /* MutelistModel.swift */,
|
||||
3AE45AF5297BB2E700C1D842 /* LibreTranslateServer.swift */,
|
||||
@@ -780,6 +792,8 @@
|
||||
4CB883A72975FC1800DC99E7 /* Zaps.swift */,
|
||||
4CB883B5297730E400DC99E7 /* LNUrls.swift */,
|
||||
3AB72AB8298ECF30004BB58C /* Translator.swift */,
|
||||
4C2CDDF6299D4A5E00879FD5 /* Debouncer.swift */,
|
||||
7C95CAED299DCEF1009DCB67 /* KFOptionSetter+.swift */,
|
||||
);
|
||||
path = Util;
|
||||
sourceTree = "<group>";
|
||||
@@ -860,6 +874,7 @@
|
||||
7C902AE22981D55B002AB16E /* ZoomableScrollView.swift */,
|
||||
4CB883AF297705DD00DC99E7 /* ZapButton.swift */,
|
||||
4C42812B298C848200DBF26F /* TranslateView.swift */,
|
||||
7CFF6316299FEFE5005D382A /* SelectableText.swift */,
|
||||
);
|
||||
path = Components;
|
||||
sourceTree = "<group>";
|
||||
@@ -1116,6 +1131,9 @@
|
||||
"zh-CN",
|
||||
"el-GR",
|
||||
ja,
|
||||
id,
|
||||
cs,
|
||||
ru,
|
||||
);
|
||||
mainGroup = 4CE6DEDA27F7A08100C66700;
|
||||
packageReferences = (
|
||||
@@ -1246,7 +1264,6 @@
|
||||
4CE8794829941DA700F758CC /* RelayFilters.swift in Sources */,
|
||||
4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */,
|
||||
4C3BEFE0281DE1ED00B3DE84 /* DamusState.swift in Sources */,
|
||||
7C45AE71297353390031D7BC /* KFImageModel.swift in Sources */,
|
||||
4CF0ABE929844AF100D66079 /* AnyCodable.swift in Sources */,
|
||||
4C0A3F8F280F640A000448DE /* ThreadModel.swift in Sources */,
|
||||
4CC7AAF2297F129C00430951 /* EmbeddedEventView.swift in Sources */,
|
||||
@@ -1254,6 +1271,7 @@
|
||||
4CC7AAE7297EFA7B00430951 /* Zap.swift in Sources */,
|
||||
4C3BEFD22819DB9B00B3DE84 /* ProfileModel.swift in Sources */,
|
||||
4C0A3F93280F66F5000448DE /* ReplyMap.swift in Sources */,
|
||||
7C95CAEE299DCEF1009DCB67 /* KFOptionSetter+.swift in Sources */,
|
||||
BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */,
|
||||
3169CAE6294E69C000EE4006 /* EmptyTimelineView.swift in Sources */,
|
||||
4CC7AAF0297F11C700430951 /* SelectedEventView.swift in Sources */,
|
||||
@@ -1327,6 +1345,7 @@
|
||||
4CE8794E2996B16A00F758CC /* RelayToggle.swift in Sources */,
|
||||
4C3AC79B28306D7B00E1F516 /* Contacts.swift in Sources */,
|
||||
4C3EA63D28FF52D600C48A62 /* bolt11.c in Sources */,
|
||||
7CFF6317299FEFE5005D382A /* SelectableText.swift in Sources */,
|
||||
4CB55EF3295E5D59007FD187 /* RecommendedRelayView.swift in Sources */,
|
||||
4CF0ABEC29844B4700D66079 /* AnyDecodable.swift in Sources */,
|
||||
4C5F9118283D88E40052CD1C /* FollowingModel.swift in Sources */,
|
||||
@@ -1342,6 +1361,7 @@
|
||||
4CE879552996BAB900F758CC /* RelayPaidDetail.swift in Sources */,
|
||||
4CF0ABD42980996B00D66079 /* Report.swift in Sources */,
|
||||
4C06670B28FDE64700038D2A /* damus.c in Sources */,
|
||||
4C2CDDF7299D4A5E00879FD5 /* Debouncer.swift in Sources */,
|
||||
3AAA95CC298E07E900F3D526 /* DeepLPlan.swift in Sources */,
|
||||
4FE60CDD295E1C5E00105A1F /* Wallet.swift in Sources */,
|
||||
3AA247FF297E3D900090C62D /* RepostsView.swift in Sources */,
|
||||
@@ -1421,6 +1441,9 @@
|
||||
3A5CAE1F298DC0DB00B5334F /* zh-CN */,
|
||||
3A25EF152992DA5D008ABE69 /* el-GR */,
|
||||
3A66D929299472FA008B44F4 /* ja */,
|
||||
3A41E55B299D52BE001FA465 /* id */,
|
||||
3A8624DB299E82BE00BD8BE9 /* cs */,
|
||||
3A827A1A299FC69D00C4D171 /* ru */,
|
||||
);
|
||||
name = Localizable.stringsdict;
|
||||
sourceTree = "<group>";
|
||||
@@ -1441,6 +1464,9 @@
|
||||
3A5CAE1D298DC0DB00B5334F /* zh-CN */,
|
||||
3A25EF132992DA5D008ABE69 /* el-GR */,
|
||||
3A66D927299472FA008B44F4 /* ja */,
|
||||
3A41E559299D52BE001FA465 /* id */,
|
||||
3A8624D9299E82BE00BD8BE9 /* cs */,
|
||||
3A827A18299FC69D00C4D171 /* ru */,
|
||||
);
|
||||
name = InfoPlist.strings;
|
||||
sourceTree = "<group>";
|
||||
@@ -1461,6 +1487,9 @@
|
||||
3A5CAE1E298DC0DB00B5334F /* zh-CN */,
|
||||
3A25EF142992DA5D008ABE69 /* el-GR */,
|
||||
3A66D928299472FA008B44F4 /* ja */,
|
||||
3A41E55A299D52BE001FA465 /* id */,
|
||||
3A8624DA299E82BE00BD8BE9 /* cs */,
|
||||
3A827A19299FC69D00C4D171 /* ru */,
|
||||
);
|
||||
name = Localizable.strings;
|
||||
sourceTree = "<group>";
|
||||
@@ -1596,7 +1625,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -1638,7 +1667,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
|
||||
@@ -11,24 +11,6 @@
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xC5",
|
||||
"green" : "0x43",
|
||||
"red" : "0xCC"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
@@ -11,24 +11,6 @@
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x00",
|
||||
"green" : "0x00",
|
||||
"red" : "0x00"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
@@ -11,24 +11,6 @@
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xFF",
|
||||
"green" : "0x4D",
|
||||
"red" : "0x4B"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
@@ -11,24 +11,6 @@
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x1E",
|
||||
"green" : "0x1C",
|
||||
"red" : "0x1C"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
@@ -11,24 +11,6 @@
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x4F",
|
||||
"green" : "0xC3",
|
||||
"red" : "0x66"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
@@ -11,24 +11,6 @@
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xF4",
|
||||
"green" : "0xEE",
|
||||
"red" : "0xEE"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
@@ -11,24 +11,6 @@
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x5F",
|
||||
"green" : "0x5F",
|
||||
"red" : "0x5F"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
@@ -11,24 +11,6 @@
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xC5",
|
||||
"green" : "0x43",
|
||||
"red" : "0xCC"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
@@ -11,24 +11,6 @@
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xFF",
|
||||
"green" : "0xFF",
|
||||
"red" : "0xFF"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic-copy.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 354 B |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic-key.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 400 B |
@@ -1,52 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic-message-black.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"filename" : "ic-message-white 1.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 321 B |
|
Before Width: | Height: | Size: 341 B |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic-nipverified.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 950 B |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic-qr.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 252 B |
@@ -2,16 +2,7 @@
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "profile-banner.jpeg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
11
damus/Assets.xcassets/bbw.imageset/Contents.json
vendored
@@ -2,16 +2,7 @@
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "bbw.jpg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
@@ -2,16 +2,7 @@
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "bitcoin-p2p.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
@@ -2,16 +2,7 @@
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "blixt-wallet.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
@@ -2,16 +2,7 @@
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "bluewallet.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
@@ -2,16 +2,7 @@
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "breez.jpg",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
@@ -2,16 +2,7 @@
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "cashapp.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
@@ -2,16 +2,7 @@
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "digital-nomad.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
@@ -2,16 +2,7 @@
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "encrypted-message.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic-lightning.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 458 B |
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic-tick.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
damus/Assets.xcassets/ic-tick.imageset/ic-tick.png
vendored
|
Before Width: | Height: | Size: 671 B |
@@ -2,16 +2,7 @@
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "lnlink.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
@@ -2,16 +2,7 @@
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "damus-nobg.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
@@ -2,16 +2,7 @@
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "muun.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
@@ -2,16 +2,7 @@
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "phoenix.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
@@ -2,16 +2,7 @@
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "river.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
@@ -2,16 +2,7 @@
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "strike.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
@@ -2,16 +2,7 @@
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "undercover.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
@@ -2,16 +2,7 @@
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "walletofsatoshi.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
@@ -2,16 +2,7 @@
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "zebedee.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
@@ -2,16 +2,7 @@
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "zeus.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
@@ -66,20 +66,11 @@ struct ImageContextMenuModifier: ViewModifier {
|
||||
|
||||
private struct ImageContainerView: View {
|
||||
|
||||
@ObservedObject var imageModel: KFImageModel
|
||||
let url: URL?
|
||||
|
||||
@State private var image: UIImage?
|
||||
@State private var showShareSheet = false
|
||||
|
||||
init(url: URL?) {
|
||||
self.imageModel = KFImageModel(
|
||||
url: url,
|
||||
fallbackUrl: nil,
|
||||
maxByteSize: 2000000, // 2 MB
|
||||
downsampleSize: CGSize(width: 400, height: 400)
|
||||
)
|
||||
}
|
||||
|
||||
private struct ImageHandler: ImageModifier {
|
||||
@Binding var handler: UIImage?
|
||||
|
||||
@@ -91,30 +82,17 @@ private struct ImageContainerView: View {
|
||||
|
||||
var body: some View {
|
||||
|
||||
KFAnimatedImage(imageModel.url)
|
||||
.callbackQueue(.dispatch(.global(qos: .background)))
|
||||
.processingQueue(.dispatch(.global(qos: .background)))
|
||||
.cacheOriginalImage()
|
||||
KFAnimatedImage(url)
|
||||
.imageContext(.note)
|
||||
.configure { view in
|
||||
view.framePreloadCount = 1
|
||||
view.framePreloadCount = 3
|
||||
}
|
||||
.scaleFactor(UIScreen.main.scale)
|
||||
.loadDiskFileSynchronously()
|
||||
.fade(duration: 0.1)
|
||||
.imageModifier(ImageHandler(handler: $image))
|
||||
.onFailure { _ in
|
||||
imageModel.downloadFailed()
|
||||
}
|
||||
.id(imageModel.refreshID)
|
||||
.clipped()
|
||||
.modifier(ImageContextMenuModifier(url: imageModel.url, image: image, showShareSheet: $showShareSheet))
|
||||
.modifier(ImageContextMenuModifier(url: url, image: image, showShareSheet: $showShareSheet))
|
||||
.sheet(isPresented: $showShareSheet) {
|
||||
ShareSheet(activityItems: [imageModel.url])
|
||||
ShareSheet(activityItems: [url])
|
||||
}
|
||||
|
||||
// TODO: Update ImageCarousel with serializer and processor
|
||||
// .serialize(by: imageModel.serializer)
|
||||
// .setProcessor(imageModel.processor)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,14 +105,6 @@ struct ImageView: View {
|
||||
@State private var selectedIndex = 0
|
||||
@State var showMenu = true
|
||||
|
||||
var safeAreaInsets: UIEdgeInsets? {
|
||||
return UIApplication
|
||||
.shared
|
||||
.connectedScenes
|
||||
.flatMap { ($0 as? UIWindowScene)?.windows ?? [] }
|
||||
.first { $0.isKeyWindow }?.safeAreaInsets
|
||||
}
|
||||
|
||||
var navBarView: some View {
|
||||
VStack {
|
||||
HStack {
|
||||
@@ -180,8 +150,8 @@ struct ImageView: View {
|
||||
ZoomableScrollView {
|
||||
ImageContainerView(url: urls[index])
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.padding(.top, safeAreaInsets?.top)
|
||||
.padding(.bottom, safeAreaInsets?.bottom)
|
||||
.padding(.top, Theme.safeAreaInsets?.top)
|
||||
.padding(.bottom, Theme.safeAreaInsets?.bottom)
|
||||
}
|
||||
.modifier(SwipeToDismissModifier(minDistance: 50, onDismiss: {
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
@@ -210,7 +180,7 @@ struct ImageView: View {
|
||||
}
|
||||
}
|
||||
.animation(.easeInOut, value: showMenu)
|
||||
.padding(.bottom, safeAreaInsets?.bottom)
|
||||
.padding(.bottom, Theme.safeAreaInsets?.bottom)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -229,12 +199,8 @@ struct ImageCarousel: View {
|
||||
.foregroundColor(Color.clear)
|
||||
.overlay {
|
||||
KFAnimatedImage(url)
|
||||
.callbackQueue(.dispatch(.global(qos: .background)))
|
||||
.processingQueue(.dispatch(.global(qos: .background)))
|
||||
.cacheOriginalImage()
|
||||
.loadDiskFileSynchronously()
|
||||
.scaleFactor(UIScreen.main.scale)
|
||||
.fade(duration: 0.1)
|
||||
.imageContext(.note)
|
||||
.cancelOnDisappear(true)
|
||||
.configure { view in
|
||||
view.framePreloadCount = 3
|
||||
}
|
||||
|
||||
96
damus/Components/SelectableText.swift
Normal file
@@ -0,0 +1,96 @@
|
||||
//
|
||||
// SelectableText.swift
|
||||
// damus
|
||||
//
|
||||
// Created by Oleg Abalonski on 2/16/23.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftUI
|
||||
|
||||
struct SelectableText: View {
|
||||
|
||||
let attributedString: AttributedString
|
||||
|
||||
@State private var selectedTextHeight: CGFloat = .zero
|
||||
@State private var selectedTextWidth: CGFloat = .zero
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geo in
|
||||
TextViewRepresentable(
|
||||
attributedString: attributedString,
|
||||
textColor: UIColor.label,
|
||||
font: UIFont.preferredFont(forTextStyle: .title2),
|
||||
fixedWidth: selectedTextWidth,
|
||||
height: $selectedTextHeight
|
||||
)
|
||||
.onAppear {
|
||||
self.selectedTextWidth = geo.size.width
|
||||
}
|
||||
.onChange(of: geo.size) { newSize in
|
||||
self.selectedTextWidth = newSize.width
|
||||
}
|
||||
}
|
||||
.frame(height: selectedTextHeight)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate struct TextViewRepresentable: UIViewRepresentable {
|
||||
|
||||
let attributedString: AttributedString
|
||||
let textColor: UIColor
|
||||
let font: UIFont
|
||||
let fixedWidth: CGFloat
|
||||
|
||||
@Binding var height: CGFloat
|
||||
|
||||
func makeUIView(context: UIViewRepresentableContext<Self>) -> UITextView {
|
||||
let view = UITextView()
|
||||
view.isEditable = false
|
||||
view.dataDetectorTypes = .all
|
||||
view.isSelectable = true
|
||||
view.textContainer.lineFragmentPadding = 0
|
||||
view.textContainerInset = .zero
|
||||
return view
|
||||
}
|
||||
|
||||
func updateUIView(_ uiView: UITextView, context: UIViewRepresentableContext<Self>) {
|
||||
let mutableAttributedString = createNSAttributedString()
|
||||
uiView.attributedText = mutableAttributedString
|
||||
|
||||
let newHeight = mutableAttributedString.height(containerWidth: fixedWidth)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
height = newHeight
|
||||
}
|
||||
}
|
||||
|
||||
func createNSAttributedString() -> NSMutableAttributedString {
|
||||
let mutableAttributedString = NSMutableAttributedString(attributedString)
|
||||
let myAttribute = [
|
||||
NSAttributedString.Key.font: font,
|
||||
NSAttributedString.Key.foregroundColor: textColor
|
||||
]
|
||||
|
||||
mutableAttributedString.addAttributes(
|
||||
myAttribute,
|
||||
range: NSRange.init(location: 0, length: mutableAttributedString.length)
|
||||
)
|
||||
|
||||
return mutableAttributedString
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate extension NSAttributedString {
|
||||
|
||||
func height(containerWidth: CGFloat) -> CGFloat {
|
||||
|
||||
let rect = self.boundingRect(
|
||||
with: CGSize.init(width: containerWidth, height: CGFloat.greatestFiniteMagnitude),
|
||||
options: [.usesLineFragmentOrigin, .usesFontLeading],
|
||||
context: nil
|
||||
)
|
||||
|
||||
return ceil(rect.size.height)
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import NaturalLanguage
|
||||
struct TranslateView: View {
|
||||
let damus_state: DamusState
|
||||
let event: NostrEvent
|
||||
let size: EventViewKind
|
||||
|
||||
@State var checkingTranslationStatus: Bool = false
|
||||
@State var currentLanguage: String = "en"
|
||||
@@ -34,9 +33,7 @@ struct TranslateView: View {
|
||||
}
|
||||
.translate_button_style()
|
||||
|
||||
Text(artifacts.content)
|
||||
.font(eventviewsize_to_font(size))
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
SelectableText(attributedString: artifacts.content)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,6 +140,6 @@ struct TranslateView: View {
|
||||
struct TranslateView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let ds = test_damus_state()
|
||||
TranslateView(damus_state: ds, event: test_event, size: .selected)
|
||||
TranslateView(damus_state: ds, event: test_event)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ struct ZapButton: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
HStack(spacing: 4) {
|
||||
EventActionButton(img: zap_img, col: zap_color) {
|
||||
if bar.zapped {
|
||||
//notify(.delete, bar.our_tip)
|
||||
@@ -110,8 +110,7 @@ struct ZapButton: View {
|
||||
}
|
||||
.accessibilityLabel(NSLocalizedString("Zap", comment: "Accessibility label for zap button"))
|
||||
|
||||
Text("\(bar.zap_total > 0 ? "\(format_msats_abbrev(bar.zap_total))" : "")")
|
||||
.offset(x: 22)
|
||||
Text(String("\(bar.zap_total > 0 ? "\(format_msats_abbrev(bar.zap_total))" : "")"))
|
||||
.font(.footnote)
|
||||
.foregroundColor(bar.zapped ? Color.orange : Color.gray)
|
||||
}
|
||||
|
||||
@@ -328,7 +328,7 @@ struct ContentView: View {
|
||||
PostView(replying_to: nil, references: [], damus_state: damus_state!)
|
||||
case .reply(let event):
|
||||
ReplyView(replying_to: event, damus: damus_state!)
|
||||
case .event(let event):
|
||||
case .event:
|
||||
EventDetailView()
|
||||
case .filter:
|
||||
let timeline = selected_timeline ?? .home
|
||||
|
||||
@@ -38,6 +38,9 @@ class HomeModel: ObservableObject {
|
||||
var channels: [String: NostrEvent] = [:]
|
||||
var last_event_of_kind: [String: [Int: NostrEvent]] = [:]
|
||||
var done_init: Bool = false
|
||||
var incoming_dms: [NostrEvent] = []
|
||||
let dm_debouncer = Debouncer(interval: 0.5)
|
||||
var should_debounce_dms = true
|
||||
|
||||
let home_subid = UUID().description
|
||||
let contacts_subid = UUID().description
|
||||
@@ -56,16 +59,25 @@ class HomeModel: ObservableObject {
|
||||
init() {
|
||||
self.damus_state = DamusState.empty
|
||||
self.dms = DirectMessagesModel(our_pubkey: damus_state.pubkey)
|
||||
self.setup_debouncer()
|
||||
}
|
||||
|
||||
init(damus_state: DamusState) {
|
||||
self.damus_state = damus_state
|
||||
self.dms = DirectMessagesModel(our_pubkey: damus_state.pubkey)
|
||||
self.setup_debouncer()
|
||||
}
|
||||
|
||||
var pool: RelayPool {
|
||||
return damus_state.pool
|
||||
}
|
||||
|
||||
func setup_debouncer() {
|
||||
// turn off debouncer after initial load
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
|
||||
self.should_debounce_dms = false
|
||||
}
|
||||
}
|
||||
|
||||
func has_sub_id_event(sub_id: String, ev_id: String) -> Bool {
|
||||
if !has_event.keys.contains(sub_id) {
|
||||
@@ -303,7 +315,8 @@ class HomeModel: ObservableObject {
|
||||
case .eose(let sub_id):
|
||||
|
||||
if sub_id == dms_subid {
|
||||
let dms = dms.dms.flatMap { $0.1.events }
|
||||
var dms = dms.dms.flatMap { $0.1.events }
|
||||
dms.append(contentsOf: incoming_dms)
|
||||
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, events: dms, damus_state: damus_state)
|
||||
} else if sub_id == notifications_subid {
|
||||
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, events: notifications, damus_state: damus_state)
|
||||
@@ -458,12 +471,11 @@ class HomeModel: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
func insert_home_event(_ ev: NostrEvent) -> Bool {
|
||||
func insert_home_event(_ ev: NostrEvent) {
|
||||
let ok = insert_uniq_sorted_event(events: &self.events, new_ev: ev, cmp: { $0.created_at > $1.created_at })
|
||||
if ok {
|
||||
handle_last_event(ev: ev, timeline: .home)
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
func handle_text_event(sub_id: String, _ ev: NostrEvent) {
|
||||
@@ -472,15 +484,33 @@ class HomeModel: ObservableObject {
|
||||
}
|
||||
|
||||
if sub_id == home_subid {
|
||||
let _ = insert_home_event(ev)
|
||||
insert_home_event(ev)
|
||||
} else if sub_id == notifications_subid {
|
||||
handle_notification(ev: ev)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func handle_dm(_ ev: NostrEvent) {
|
||||
if let notifs = handle_incoming_dm(contacts: damus_state.contacts, prev_events: self.new_events, dms: self.dms, our_pubkey: self.damus_state.pubkey, ev: ev) {
|
||||
self.new_events = notifs
|
||||
guard should_show_event(contacts: damus_state.contacts, ev: ev) else {
|
||||
return
|
||||
}
|
||||
|
||||
if !should_debounce_dms {
|
||||
self.incoming_dms.append(ev)
|
||||
if let notifs = handle_incoming_dms(prev_events: self.new_events, dms: self.dms, our_pubkey: self.damus_state.pubkey, evs: self.incoming_dms) {
|
||||
self.new_events = notifs
|
||||
}
|
||||
self.incoming_dms = []
|
||||
return
|
||||
}
|
||||
|
||||
incoming_dms.append(ev)
|
||||
|
||||
dm_debouncer.debounce {
|
||||
if let notifs = handle_incoming_dms(prev_events: self.new_events, dms: self.dms, our_pubkey: self.damus_state.pubkey, evs: self.incoming_dms) {
|
||||
self.new_events = notifs
|
||||
}
|
||||
self.incoming_dms = []
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -626,14 +656,14 @@ func process_metadata_event(our_pubkey: String, profiles: Profiles, ev: NostrEve
|
||||
|
||||
// load pfps asap
|
||||
let picture = tprof.profile.picture ?? robohash(ev.pubkey)
|
||||
if let _ = URL(string: picture) {
|
||||
if URL(string: picture) != nil {
|
||||
DispatchQueue.main.async {
|
||||
notify(.profile_updated, ProfileUpdate(pubkey: ev.pubkey, profile: profile))
|
||||
}
|
||||
}
|
||||
|
||||
let banner = tprof.profile.banner ?? ""
|
||||
if let _ = URL(string: banner) {
|
||||
if URL(string: banner) != nil {
|
||||
DispatchQueue.main.async {
|
||||
notify(.profile_updated, ProfileUpdate(pubkey: ev.pubkey, profile: profile))
|
||||
}
|
||||
@@ -761,14 +791,11 @@ func fetch_relay_metadata(relay_id: String) async throws -> RelayMetadata? {
|
||||
func process_relay_metadata() {
|
||||
}
|
||||
|
||||
func handle_incoming_dm(contacts: Contacts, prev_events: NewEventsBits, dms: DirectMessagesModel, our_pubkey: String, ev: NostrEvent) -> NewEventsBits? {
|
||||
// hide blocked users
|
||||
guard should_show_event(contacts: contacts, ev: ev) else {
|
||||
return prev_events
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func handle_incoming_dm(ev: NostrEvent, our_pubkey: String, dms: DirectMessagesModel, prev_events: NewEventsBits) -> (Bool, NewEventsBits?) {
|
||||
var inserted = false
|
||||
var found = false
|
||||
|
||||
let ours = ev.pubkey == our_pubkey
|
||||
var i = 0
|
||||
|
||||
@@ -795,15 +822,34 @@ func handle_incoming_dm(contacts: Contacts, prev_events: NewEventsBits, dms: Dir
|
||||
}
|
||||
|
||||
if !found {
|
||||
inserted = true
|
||||
let model = DirectMessageModel(events: [ev], our_pubkey: our_pubkey)
|
||||
dms.dms.append((the_pk, model))
|
||||
inserted = true
|
||||
}
|
||||
|
||||
var new_bits: NewEventsBits? = nil
|
||||
if inserted {
|
||||
new_bits = handle_last_events(new_events: prev_events, ev: ev, timeline: .dms, shouldNotify: !ours)
|
||||
}
|
||||
|
||||
return (inserted, new_bits)
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func handle_incoming_dms(prev_events: NewEventsBits, dms: DirectMessagesModel, our_pubkey: String, evs: [NostrEvent]) -> NewEventsBits? {
|
||||
var inserted = false
|
||||
|
||||
var new_events: NewEventsBits? = nil
|
||||
|
||||
for ev in evs {
|
||||
let res = handle_incoming_dm(ev: ev, our_pubkey: our_pubkey, dms: dms, prev_events: prev_events)
|
||||
inserted = res.0 || inserted
|
||||
if let new = res.1 {
|
||||
new_events = new
|
||||
}
|
||||
}
|
||||
|
||||
if inserted {
|
||||
new_events = handle_last_events(new_events: prev_events, ev: ev, timeline: .dms, shouldNotify: !ours)
|
||||
|
||||
dms.dms = dms.dms.filter({ $0.1.events.count > 0 }).sorted { a, b in
|
||||
return a.1.events.last!.created_at > b.1.events.last!.created_at
|
||||
}
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
//
|
||||
// KFImageModel.swift
|
||||
// damus
|
||||
//
|
||||
// Created by Oleg Abalonski on 1/11/23.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Kingfisher
|
||||
|
||||
class KFImageModel: ObservableObject {
|
||||
|
||||
let url: URL?
|
||||
let fallbackUrl: URL?
|
||||
let processor: ImageProcessor
|
||||
let serializer: CacheSerializer
|
||||
|
||||
@Published var refreshID = ""
|
||||
|
||||
init(url: URL?, fallbackUrl: URL?, maxByteSize: Int, downsampleSize: CGSize) {
|
||||
self.url = url
|
||||
self.fallbackUrl = fallbackUrl
|
||||
self.processor = CustomImageProcessor(maxSize: maxByteSize, downsampleSize: downsampleSize)
|
||||
self.serializer = CustomCacheSerializer(maxSize: maxByteSize, downsampleSize: downsampleSize)
|
||||
}
|
||||
|
||||
func refresh() -> Void {
|
||||
DispatchQueue.main.async {
|
||||
self.refreshID = UUID().uuidString
|
||||
}
|
||||
}
|
||||
|
||||
func cache(_ image: UIImage, forKey key: String) -> Void {
|
||||
KingfisherManager.shared.cache.store(image, forKey: key, processorIdentifier: processor.identifier) { _ in
|
||||
self.refresh()
|
||||
}
|
||||
}
|
||||
|
||||
func downloadFailed() -> Void {
|
||||
guard let url = url, let fallbackUrl = fallbackUrl else { return }
|
||||
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
KingfisherManager.shared.downloader.downloadImage(with: fallbackUrl) { result in
|
||||
|
||||
var fallbackImage: UIImage {
|
||||
switch result {
|
||||
case .success(let imageLoadingResult):
|
||||
return imageLoadingResult.image
|
||||
case .failure(let error):
|
||||
print(error)
|
||||
return UIImage()
|
||||
}
|
||||
}
|
||||
|
||||
self.cache(fallbackImage, forKey: url.absoluteString)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CustomImageProcessor: ImageProcessor {
|
||||
|
||||
let maxSize: Int
|
||||
let downsampleSize: CGSize
|
||||
|
||||
let identifier = "com.damus.customimageprocessor"
|
||||
|
||||
func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? {
|
||||
|
||||
switch item {
|
||||
case .image(_):
|
||||
// This case will never run
|
||||
return DefaultImageProcessor.default.process(item: item, options: options)
|
||||
case .data(let data):
|
||||
|
||||
// Handle large image size
|
||||
if data.count > maxSize {
|
||||
return KingfisherWrapper.downsampledImage(data: data, to: downsampleSize, scale: options.scaleFactor)
|
||||
}
|
||||
|
||||
// Handle SVG image
|
||||
if let dataString = String(data: data, encoding: .utf8),
|
||||
let svg = SVG(dataString) {
|
||||
|
||||
let render = UIGraphicsImageRenderer(size: svg.size)
|
||||
let image = render.image { context in
|
||||
svg.draw(in: context.cgContext)
|
||||
}
|
||||
|
||||
return image.kf.scaled(to: options.scaleFactor)
|
||||
}
|
||||
|
||||
return DefaultImageProcessor.default.process(item: item, options: options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CustomCacheSerializer: CacheSerializer {
|
||||
|
||||
let maxSize: Int
|
||||
let downsampleSize: CGSize
|
||||
|
||||
func data(with image: Kingfisher.KFCrossPlatformImage, original: Data?) -> Data? {
|
||||
return DefaultCacheSerializer.default.data(with: image, original: original)
|
||||
}
|
||||
|
||||
func image(with data: Data, options: Kingfisher.KingfisherParsedOptionsInfo) -> Kingfisher.KFCrossPlatformImage? {
|
||||
if data.count > maxSize {
|
||||
return KingfisherWrapper.downsampledImage(data: data, to: downsampleSize, scale: options.scaleFactor)
|
||||
}
|
||||
|
||||
return DefaultCacheSerializer.default.image(with: data, options: options)
|
||||
}
|
||||
}
|
||||
@@ -211,6 +211,32 @@ enum Amount: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
func format_actions_abbrev(_ actions: Int) -> String {
|
||||
let formatter = NumberFormatter()
|
||||
formatter.numberStyle = .decimal
|
||||
formatter.positiveSuffix = "m"
|
||||
formatter.positivePrefix = ""
|
||||
formatter.minimumFractionDigits = 0
|
||||
formatter.maximumFractionDigits = 3
|
||||
formatter.roundingMode = .down
|
||||
formatter.roundingIncrement = 0.1
|
||||
formatter.multiplier = 1
|
||||
|
||||
if actions >= 1_000_000 {
|
||||
formatter.positiveSuffix = "m"
|
||||
formatter.multiplier = 0.000001
|
||||
} else if actions >= 1000 {
|
||||
formatter.positiveSuffix = "k"
|
||||
formatter.multiplier = 0.001
|
||||
} else {
|
||||
return "\(actions)"
|
||||
}
|
||||
|
||||
let actions = NSNumber(value: actions)
|
||||
|
||||
return formatter.string(from: actions) ?? "\(actions)"
|
||||
}
|
||||
|
||||
func format_msats_abbrev(_ msats: Int64) -> String {
|
||||
let formatter = NumberFormatter()
|
||||
formatter.numberStyle = .decimal
|
||||
|
||||
@@ -111,7 +111,7 @@ class ProfileModel: ObservableObject, Equatable {
|
||||
return
|
||||
}
|
||||
if ev.is_textlike || ev.known_kind == .boost {
|
||||
let _ = insert_uniq_sorted_event(events: &self.events, new_ev: ev, cmp: { $0.created_at > $1.created_at})
|
||||
insert_uniq_sorted_event(events: &self.events, new_ev: ev, cmp: { $0.created_at > $1.created_at})
|
||||
} else if ev.known_kind == .contacts {
|
||||
handle_profile_contact_event(ev)
|
||||
} else if ev.known_kind == .metadata {
|
||||
|
||||
@@ -61,7 +61,7 @@ class SearchHomeModel: ObservableObject {
|
||||
}
|
||||
seen_pubkey.insert(ev.pubkey)
|
||||
|
||||
let _ = insert_uniq_sorted_event(events: &events, new_ev: ev) {
|
||||
insert_uniq_sorted_event(events: &events, new_ev: ev) {
|
||||
$0.created_at > $1.created_at
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ func char_to_hex(_ c: UInt8) -> UInt8?
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
@discardableResult
|
||||
func hex_decode(_ str: String) -> [UInt8]?
|
||||
{
|
||||
if str.count == 0 {
|
||||
|
||||
@@ -89,9 +89,20 @@ class RelayConnection: WebSocketDelegate {
|
||||
self.isConnected = false
|
||||
|
||||
case .text(let txt):
|
||||
if let ev = decode_nostr_event(txt: txt) {
|
||||
handleEvent(.nostr_event(ev))
|
||||
return
|
||||
if txt.count > 2000 {
|
||||
DispatchQueue.global(qos: .default).async {
|
||||
if let ev = decode_nostr_event(txt: txt) {
|
||||
DispatchQueue.main.async {
|
||||
self.handleEvent(.nostr_event(ev))
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let ev = decode_nostr_event(txt: txt) {
|
||||
handleEvent(.nostr_event(ev))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
print("decode failed for \(txt)")
|
||||
|
||||
@@ -195,27 +195,13 @@ class RelayPool {
|
||||
relay.connection.send(req)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func get_relays(_ ids: [String]) -> [Relay] {
|
||||
var relays: [Relay] = []
|
||||
|
||||
for id in ids {
|
||||
if let relay = get_relay(id) {
|
||||
relays.append(relay)
|
||||
}
|
||||
}
|
||||
|
||||
return relays
|
||||
relays.filter { ids.contains($0.id) }
|
||||
}
|
||||
|
||||
|
||||
func get_relay(_ id: String) -> Relay? {
|
||||
for relay in relays {
|
||||
if relay.id == id {
|
||||
return relay
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
relays.first(where: { $0.id == id })
|
||||
}
|
||||
|
||||
func record_last_pong(relay_id: String, event: NostrConnectionEvent) {
|
||||
|
||||
@@ -143,6 +143,7 @@ func eightToFiveBits(_ input: [UInt8]) -> [UInt8] {
|
||||
}
|
||||
|
||||
/// Decode Bech32 string
|
||||
@discardableResult
|
||||
public func bech32_decode(_ str: String) throws -> (hrp: String, data: Data)? {
|
||||
guard let strBytes = str.data(using: .utf8) else {
|
||||
throw Bech32Error.nonUTF8String
|
||||
|
||||
27
damus/Util/Debouncer.swift
Normal file
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// Debouncer.swift
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2023-02-15.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class Debouncer {
|
||||
private let queue = DispatchQueue.main
|
||||
private var workItem: DispatchWorkItem?
|
||||
private var interval: TimeInterval
|
||||
|
||||
init(interval: TimeInterval) {
|
||||
self.interval = interval
|
||||
}
|
||||
|
||||
func debounce(action: @escaping () -> Void) {
|
||||
// Cancel the previous work item if it hasn't yet executed
|
||||
workItem?.cancel()
|
||||
|
||||
// Create a new work item with a delay
|
||||
workItem = DispatchWorkItem { action() }
|
||||
queue.asyncAfter(deadline: .now() + interval, execute: workItem!)
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,7 @@ func insert_uniq_by_pubkey(events: inout [NostrEvent], new_ev: NostrEvent, cmp:
|
||||
return true
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func insert_uniq_sorted_zap(zaps: inout [Zap], new_zap: Zap) -> Bool {
|
||||
var i: Int = 0
|
||||
|
||||
@@ -58,6 +59,7 @@ func insert_uniq_sorted_zap(zaps: inout [Zap], new_zap: Zap) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func insert_uniq_sorted_event(events: inout [NostrEvent], new_ev: NostrEvent, cmp: (NostrEvent, NostrEvent) -> Bool) -> Bool {
|
||||
var i: Int = 0
|
||||
|
||||
|
||||
148
damus/Util/KFOptionSetter+.swift
Normal file
@@ -0,0 +1,148 @@
|
||||
//
|
||||
// KFOptionSetter+.swift
|
||||
// damus
|
||||
//
|
||||
// Created by Oleg Abalonski on 2/15/23.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import Kingfisher
|
||||
|
||||
extension KFOptionSetter {
|
||||
|
||||
func imageContext(_ imageContext: ImageContext) -> Self {
|
||||
options.callbackQueue = .dispatch(.global(qos: .background))
|
||||
options.processingQueue = .dispatch(.global(qos: .background))
|
||||
options.downloader = CustomImageDownloader.shared
|
||||
options.backgroundDecode = true
|
||||
options.cacheOriginalImage = true
|
||||
options.scaleFactor = UIScreen.main.scale
|
||||
|
||||
options.processor = CustomImageProcessor(
|
||||
maxSize: imageContext.maxMebibyteSize(),
|
||||
downsampleSize: imageContext.downsampleSize()
|
||||
)
|
||||
|
||||
options.cacheSerializer = CustomCacheSerializer(
|
||||
maxSize: imageContext.maxMebibyteSize(),
|
||||
downsampleSize: imageContext.downsampleSize()
|
||||
)
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func onFailure(fallbackUrl: URL?, cacheKey: String?) -> Self {
|
||||
guard let url = fallbackUrl, let key = cacheKey else { return self }
|
||||
let imageResource = ImageResource(downloadURL: url, cacheKey: key)
|
||||
let source = imageResource.convertToSource()
|
||||
options.alternativeSources = [source]
|
||||
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
let MAX_FILE_SIZE = 20_971_520 // 20MiB
|
||||
|
||||
enum ImageContext {
|
||||
case pfp
|
||||
case banner
|
||||
case note
|
||||
|
||||
func maxMebibyteSize() -> Int {
|
||||
switch self {
|
||||
case .pfp:
|
||||
return 5_242_880 // 5Mib
|
||||
case .banner, .note:
|
||||
return 20_971_520 // 20MiB
|
||||
}
|
||||
}
|
||||
|
||||
func downsampleSize() -> CGSize {
|
||||
switch self {
|
||||
case .pfp:
|
||||
return CGSize(width: 200, height: 200)
|
||||
case .banner:
|
||||
return CGSize(width: 750, height: 250)
|
||||
case .note:
|
||||
return CGSize(width: 500, height: 500)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CustomImageProcessor: ImageProcessor {
|
||||
|
||||
let maxSize: Int
|
||||
let downsampleSize: CGSize
|
||||
|
||||
let identifier = "com.damus.customimageprocessor"
|
||||
|
||||
func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? {
|
||||
|
||||
switch item {
|
||||
case .image(_):
|
||||
// This case will never run
|
||||
return DefaultImageProcessor.default.process(item: item, options: options)
|
||||
case .data(let data):
|
||||
|
||||
// Handle large image size
|
||||
if data.count > maxSize {
|
||||
return KingfisherWrapper.downsampledImage(data: data, to: downsampleSize, scale: options.scaleFactor)
|
||||
}
|
||||
|
||||
// Handle SVG image
|
||||
if let dataString = String(data: data, encoding: .utf8),
|
||||
let svg = SVG(dataString) {
|
||||
|
||||
let render = UIGraphicsImageRenderer(size: svg.size)
|
||||
let image = render.image { context in
|
||||
svg.draw(in: context.cgContext)
|
||||
}
|
||||
|
||||
return image.kf.scaled(to: options.scaleFactor)
|
||||
}
|
||||
|
||||
return DefaultImageProcessor.default.process(item: item, options: options)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct CustomCacheSerializer: CacheSerializer {
|
||||
|
||||
let maxSize: Int
|
||||
let downsampleSize: CGSize
|
||||
|
||||
func data(with image: Kingfisher.KFCrossPlatformImage, original: Data?) -> Data? {
|
||||
return DefaultCacheSerializer.default.data(with: image, original: original)
|
||||
}
|
||||
|
||||
func image(with data: Data, options: Kingfisher.KingfisherParsedOptionsInfo) -> Kingfisher.KFCrossPlatformImage? {
|
||||
if data.count > maxSize {
|
||||
return KingfisherWrapper.downsampledImage(data: data, to: downsampleSize, scale: options.scaleFactor)
|
||||
}
|
||||
|
||||
return DefaultCacheSerializer.default.image(with: data, options: options)
|
||||
}
|
||||
}
|
||||
|
||||
class CustomSessionDelegate: SessionDelegate {
|
||||
override func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
|
||||
let contentLength = response.expectedContentLength
|
||||
|
||||
// Content-Length header is optional (-1 when missing)
|
||||
if (contentLength != -1 && contentLength > MAX_FILE_SIZE) {
|
||||
return super.urlSession(session, dataTask: dataTask, didReceive: URLResponse(), completionHandler: completionHandler)
|
||||
}
|
||||
|
||||
super.urlSession(session, dataTask: dataTask, didReceive: response, completionHandler: completionHandler)
|
||||
}
|
||||
}
|
||||
|
||||
class CustomImageDownloader: ImageDownloader {
|
||||
|
||||
static let shared = CustomImageDownloader(name: "shared")
|
||||
|
||||
override init(name: String) {
|
||||
super.init(name: name)
|
||||
sessionDelegate = CustomSessionDelegate()
|
||||
}
|
||||
}
|
||||
@@ -25,4 +25,12 @@ class Theme {
|
||||
|
||||
UINavigationBar.appearance().tintColor = tintColor ?? titleColor ?? .black
|
||||
}
|
||||
|
||||
static var safeAreaInsets: UIEdgeInsets? {
|
||||
return UIApplication
|
||||
.shared
|
||||
.connectedScenes
|
||||
.flatMap { ($0 as? UIWindowScene)?.windows ?? [] }
|
||||
.first { $0.isKeyWindow }?.safeAreaInsets
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ class Zaps {
|
||||
if our_zaps[note_target.note_id] == nil {
|
||||
our_zaps[note_target.note_id] = [zap]
|
||||
} else {
|
||||
let _ = insert_uniq_sorted_zap(zaps: &(our_zaps[note_target.note_id]!), new_zap: zap)
|
||||
insert_uniq_sorted_zap(zaps: &(our_zaps[note_target.note_id]!), new_zap: zap)
|
||||
}
|
||||
case .profile(_):
|
||||
break
|
||||
@@ -61,7 +61,5 @@ class Zaps {
|
||||
event_totals[id] = event_totals[id]! + zap.invoice.amount
|
||||
|
||||
notify(.update_stats, zap.target.id)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ struct EventActionBar: View {
|
||||
.accessibilityLabel(NSLocalizedString("Reply", comment: "Accessibility label for reply button"))
|
||||
}
|
||||
Spacer()
|
||||
ZStack {
|
||||
HStack(spacing: 4) {
|
||||
|
||||
EventActionButton(img: "arrow.2.squarepath", col: bar.boosted ? Color.green : nil) {
|
||||
if bar.boosted {
|
||||
@@ -61,14 +61,13 @@ struct EventActionBar: View {
|
||||
}
|
||||
}
|
||||
.accessibilityLabel(NSLocalizedString("Boosts", comment: "Accessibility label for boosts button"))
|
||||
Text("\(bar.boosts > 0 ? "\(bar.boosts)" : "")")
|
||||
.offset(x: 18)
|
||||
Text(verbatim: "\(bar.boosts > 0 ? "\(bar.boosts)" : "")")
|
||||
.font(.footnote.weight(.medium))
|
||||
.foregroundColor(bar.boosted ? Color.green : Color.gray)
|
||||
}
|
||||
Spacer()
|
||||
|
||||
ZStack {
|
||||
HStack(spacing: 4) {
|
||||
LikeButton(liked: bar.liked) {
|
||||
if bar.liked {
|
||||
notify(.delete, bar.our_like)
|
||||
@@ -76,8 +75,7 @@ struct EventActionBar: View {
|
||||
send_like()
|
||||
}
|
||||
}
|
||||
Text("\(bar.likes > 0 ? "\(bar.likes)" : "")")
|
||||
.offset(x: 22)
|
||||
Text(verbatim: "\(bar.likes > 0 ? "\(bar.likes)" : "")")
|
||||
.font(.footnote.weight(.medium))
|
||||
.foregroundColor(bar.liked ? Color.accentColor : Color.gray)
|
||||
|
||||
@@ -158,9 +156,9 @@ struct EventActionBar: View {
|
||||
|
||||
func EventActionButton(img: String, col: Color?, action: @escaping () -> ()) -> some View {
|
||||
Button(action: action) {
|
||||
Label(NSLocalizedString("\u{00A0}", comment: "Non-breaking space character to fill in blank space next to event action button icons."), systemImage: img)
|
||||
.font(.footnote.weight(.medium))
|
||||
Image(systemName: img)
|
||||
.foregroundColor(col == nil ? Color.gray : col!)
|
||||
.font(.footnote.weight(.medium))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,6 +188,8 @@ struct EventActionBar_Previews: PreviewProvider {
|
||||
let likedbar = ActionBarModel(likes: 10, boosts: 0, zaps: 0, zap_total: 0, our_like: nil, our_boost: nil, our_zap: nil)
|
||||
let likedbar_ours = ActionBarModel(likes: 10, boosts: 0, zaps: 0, zap_total: 0, our_like: NostrEvent(id: "", content: "", pubkey: ""), our_boost: nil, our_zap: nil)
|
||||
let maxed_bar = ActionBarModel(likes: 999, boosts: 999, zaps: 999, zap_total: 99999999, our_like: NostrEvent(id: "", content: "", pubkey: ""), our_boost: NostrEvent(id: "", content: "", pubkey: ""), our_zap: nil)
|
||||
let extra_max_bar = ActionBarModel(likes: 9999, boosts: 9999, zaps: 9999, zap_total: 99999999, our_like: NostrEvent(id: "", content: "", pubkey: ""), our_boost: NostrEvent(id: "", content: "", pubkey: ""), our_zap: nil)
|
||||
let mega_max_bar = ActionBarModel(likes: 9999999, boosts: 99999, zaps: 9999, zap_total: 99999999, our_like: NostrEvent(id: "", content: "", pubkey: ""), our_boost: NostrEvent(id: "", content: "", pubkey: ""), our_zap: nil)
|
||||
let zapbar = ActionBarModel(likes: 0, boosts: 0, zaps: 5, zap_total: 10000000, our_like: nil, our_boost: nil, our_zap: nil)
|
||||
|
||||
VStack(spacing: 50) {
|
||||
@@ -200,7 +200,11 @@ struct EventActionBar_Previews: PreviewProvider {
|
||||
EventActionBar(damus_state: ds, event: ev, bar: likedbar_ours)
|
||||
|
||||
EventActionBar(damus_state: ds, event: ev, bar: maxed_bar)
|
||||
|
||||
EventActionBar(damus_state: ds, event: ev, bar: extra_max_bar)
|
||||
|
||||
EventActionBar(damus_state: ds, event: ev, bar: mega_max_bar)
|
||||
|
||||
EventActionBar(damus_state: ds, event: ev, bar: zapbar, test_lnurl: "lnurl")
|
||||
}
|
||||
.padding(20)
|
||||
|
||||
@@ -26,14 +26,14 @@ struct EventDetailBar: View {
|
||||
HStack {
|
||||
if bar.boosts > 0 {
|
||||
NavigationLink(destination: RepostsView(damus_state: state, model: RepostsModel(state: state, target: target))) {
|
||||
Text("\(Text("\(bar.boosts)", comment: "Number of reposts.").font(.body.bold())) \(Text(String(format: NSLocalizedString("reposts_count", comment: "Part of a larger sentence to describe how many reposts there are."), bar.boosts)).foregroundColor(.gray))", comment: "Sentence composed of 2 variables to describe how many reposts. In source English, the first variable is the number of reposts, and the second variable is 'Repost' or 'Reposts'.")
|
||||
Text("\(Text(verbatim: "\(bar.boosts)").font(.body.bold())) \(Text(String(format: NSLocalizedString("reposts_count", comment: "Part of a larger sentence to describe how many reposts there are."), bar.boosts)).foregroundColor(.gray))", comment: "Sentence composed of 2 variables to describe how many reposts. In source English, the first variable is the number of reposts, and the second variable is 'Repost' or 'Reposts'.")
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
}
|
||||
|
||||
if bar.likes > 0 {
|
||||
NavigationLink(destination: ReactionsView(damus_state: state, model: ReactionsModel(state: state, target: target))) {
|
||||
Text("\(Text("\(bar.likes)", comment: "Number of reactions on a post.").font(.body.bold())) \(Text(String(format: NSLocalizedString("reactions_count", comment: "Part of a larger sentence to describe how many reactions there are on a post."), bar.likes)).foregroundColor(.gray))", comment: "Sentence composed of 2 variables to describe how many reactions there are on a post. In source English, the first variable is the number of reactions, and the second variable is 'Reaction' or 'Reactions'.")
|
||||
Text("\(Text(verbatim: "\(bar.likes)").font(.body.bold())) \(Text(String(format: NSLocalizedString("reactions_count", comment: "Part of a larger sentence to describe how many reactions there are on a post."), bar.likes)).foregroundColor(.gray))", comment: "Sentence composed of 2 variables to describe how many reactions there are on a post. In source English, the first variable is the number of reactions, and the second variable is 'Reaction' or 'Reactions'.")
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
}
|
||||
@@ -41,7 +41,7 @@ struct EventDetailBar: View {
|
||||
if bar.zaps > 0 {
|
||||
let dst = ZapsView(state: state, target: .note(id: target, author: target_pk))
|
||||
NavigationLink(destination: dst) {
|
||||
Text("\(Text("\(bar.zaps)", comment: "Number of zap payments on a post.").font(.body.bold())) \(Text(String(format: NSLocalizedString("zaps_count", comment: "Part of a larger sentence to describe how many zap payments there are on a post."), bar.boosts)).foregroundColor(.gray))", comment: "Sentence composed of 2 variables to describe how many zap payments there are on a post. In source English, the first variable is the number of zap payments, and the second variable is 'Zap' or 'Zaps'.")
|
||||
Text("\(Text(verbatim: "\(bar.zaps)").font(.body.bold())) \(Text(String(format: NSLocalizedString("zaps_count", comment: "Part of a larger sentence to describe how many zap payments there are on a post."), bar.zaps)).foregroundColor(.gray))", comment: "Sentence composed of 2 variables to describe how many zap payments there are on a post. In source English, the first variable is the number of zap payments, and the second variable is 'Zap' or 'Zaps'.")
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
}
|
||||
|
||||
@@ -10,40 +10,23 @@ import Kingfisher
|
||||
|
||||
struct InnerBannerImageView: View {
|
||||
|
||||
let url: URL?
|
||||
let defaultImage = UIImage(named: "profile-banner") ?? UIImage()
|
||||
|
||||
@ObservedObject var imageModel: KFImageModel
|
||||
|
||||
init(url: URL?) {
|
||||
self.imageModel = KFImageModel(
|
||||
url: url,
|
||||
fallbackUrl: nil,
|
||||
maxByteSize: 20_971_520, // 20 MiB
|
||||
downsampleSize: CGSize(width: 750, height: 250)
|
||||
)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Color(uiColor: .systemBackground)
|
||||
|
||||
if (imageModel.url != nil) {
|
||||
KFAnimatedImage(imageModel.url)
|
||||
.callbackQueue(.dispatch(.global(qos: .background)))
|
||||
.processingQueue(.dispatch(.global(qos: .background)))
|
||||
.serialize(by: imageModel.serializer)
|
||||
.setProcessor(imageModel.processor)
|
||||
if (url != nil) {
|
||||
KFAnimatedImage(url)
|
||||
.imageContext(.banner)
|
||||
.configure { view in
|
||||
view.framePreloadCount = 1
|
||||
view.framePreloadCount = 3
|
||||
}
|
||||
.placeholder { _ in
|
||||
Color(uiColor: .secondarySystemBackground)
|
||||
}
|
||||
.scaleFactor(UIScreen.main.scale)
|
||||
.loadDiskFileSynchronously()
|
||||
.fade(duration: 0.1)
|
||||
.onFailureImage(defaultImage)
|
||||
.id(imageModel.refreshID)
|
||||
} else {
|
||||
Image(uiImage: defaultImage).resizable()
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ struct ChatView: View {
|
||||
}
|
||||
|
||||
var ReplyDescription: some View {
|
||||
Text("\(reply_desc(profiles: damus_state.profiles, event: event))")
|
||||
Text(verbatim: "\(reply_desc(profiles: damus_state.profiles, event: event))")
|
||||
.font(.footnote)
|
||||
.foregroundColor(.gray)
|
||||
.frame(alignment: .leading)
|
||||
@@ -89,7 +89,7 @@ struct ChatView: View {
|
||||
ProfileName(pubkey: event.pubkey, profile: damus_state.profiles.lookup(id: event.pubkey), damus: damus_state, show_friend_confirmed: true)
|
||||
.foregroundColor(colorScheme == .dark ? id_to_color(event.pubkey) : Color.black)
|
||||
//.shadow(color: Color.black, radius: 2)
|
||||
Text("\(format_relative_time(event.created_at))")
|
||||
Text(verbatim: "\(format_relative_time(event.created_at))")
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
}
|
||||
@@ -112,8 +112,8 @@ struct ChatView: View {
|
||||
NoteContentView(damus_state: damus_state,
|
||||
event: event,
|
||||
show_images: show_images,
|
||||
artifacts: .just_content(event.content),
|
||||
size: .normal)
|
||||
size: .normal,
|
||||
artifacts: .just_content(event.content))
|
||||
|
||||
if is_active || next_ev == nil || next_ev!.pubkey != event.pubkey {
|
||||
let bar = make_actionbar_model(ev: event.id, damus: damus_state)
|
||||
|
||||
@@ -8,6 +8,7 @@ import AVFoundation
|
||||
import Kingfisher
|
||||
import SwiftUI
|
||||
import LocalAuthentication
|
||||
import Combine
|
||||
|
||||
struct ConfigView: View {
|
||||
let state: DamusState
|
||||
@@ -130,7 +131,25 @@ struct ConfigView: View {
|
||||
|
||||
|
||||
Section(NSLocalizedString("Default Zap Amount in sats", comment: "Section title for zap configuration")) {
|
||||
TextField("1000", text: $default_zap_amount)
|
||||
TextField(String("1000"), text: $default_zap_amount)
|
||||
.keyboardType(.numberPad)
|
||||
.onReceive(Just(default_zap_amount)) { newValue in
|
||||
let filtered = newValue.filter { Set("0123456789").contains($0) }
|
||||
|
||||
if filtered != newValue {
|
||||
default_zap_amount = filtered
|
||||
}
|
||||
|
||||
if filtered == "" {
|
||||
set_default_zap_amount(pubkey: state.pubkey, amount: 1000)
|
||||
return
|
||||
}
|
||||
|
||||
guard let amt = Int(filtered) else {
|
||||
return
|
||||
}
|
||||
set_default_zap_amount(pubkey: state.pubkey, amount: amt)
|
||||
}
|
||||
}
|
||||
|
||||
Section(NSLocalizedString("Translations", comment: "Section title for selecting the translation service.")) {
|
||||
@@ -204,16 +223,10 @@ struct ConfigView: View {
|
||||
let bundleShortVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as! String
|
||||
let bundleVersion = Bundle.main.infoDictionary?["CFBundleVersion"] as! String
|
||||
Section(NSLocalizedString("Version", comment: "Section title for displaying the version number of the Damus app.")) {
|
||||
Text("\(bundleShortVersion) (\(bundleVersion))", comment: "Text indicating which version of the Damus app is running. Should typically not need to be translated.")
|
||||
Text(verbatim: "\(bundleShortVersion) (\(bundleVersion))")
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: default_zap_amount) { val in
|
||||
guard let amt = Int(val) else {
|
||||
return
|
||||
}
|
||||
set_default_zap_amount(pubkey: state.pubkey, amount: amt)
|
||||
}
|
||||
.navigationTitle(NSLocalizedString("Settings", comment: "Navigation title for Settings view."))
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
.alert(NSLocalizedString("Permanently Delete Account", comment: "Alert for deleting the users account."), isPresented: $confirm_delete_account) {
|
||||
|
||||
@@ -36,14 +36,14 @@ struct CreateAccountView: View {
|
||||
|
||||
HStack(alignment: .top) {
|
||||
VStack {
|
||||
Text(" ", comment: "Blank space to separate profile picture from profile editor form.")
|
||||
Text(verbatim: " ")
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
VStack {
|
||||
SignupForm {
|
||||
FormLabel(NSLocalizedString("Username", comment: "Label to prompt username entry."))
|
||||
HStack(spacing: 0.0) {
|
||||
Text("@", comment: "Prefix character to username.")
|
||||
Text(verbatim: "@")
|
||||
.foregroundColor(.white)
|
||||
.padding(.leading, -25.0)
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ struct DMView: View {
|
||||
|
||||
let should_show_img = should_show_images(contacts: damus_state.contacts, ev: event, our_pubkey: damus_state.pubkey)
|
||||
|
||||
NoteContentView(damus_state: damus_state, event: event, show_images: should_show_img, artifacts: .just_content(event.get_content(damus_state.keypair.privkey)), size: .normal)
|
||||
NoteContentView(damus_state: damus_state, event: event, show_images: should_show_img, size: .normal, artifacts: .just_content(event.get_content(damus_state.keypair.privkey)))
|
||||
.foregroundColor(is_ours ? Color.white : Color.primary)
|
||||
.padding(10)
|
||||
.background(is_ours ? Color.accentColor : Color.secondary.opacity(0.15))
|
||||
|
||||
@@ -33,13 +33,14 @@ struct DirectMessagesView: View {
|
||||
NavigationLink(destination: chat, isActive: $open_dm) {
|
||||
EmptyView()
|
||||
}
|
||||
LazyVStack {
|
||||
LazyVStack(spacing: 0) {
|
||||
if model.dms.isEmpty, !model.loading {
|
||||
EmptyTimelineView()
|
||||
} else {
|
||||
let dms = requests ? model.message_requests : model.friend_dms
|
||||
ForEach(dms, id: \.0) { tup in
|
||||
MaybeEvent(tup)
|
||||
.padding(.top, 10)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import SwiftUI
|
||||
|
||||
struct EventDetailView: View {
|
||||
var body: some View {
|
||||
Text("EventDetailView")
|
||||
Text(verbatim: "EventDetailView")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,14 +33,14 @@ func print_event(_ ev: NostrEvent) {
|
||||
print(ev.description)
|
||||
}
|
||||
|
||||
func scroll_to_event(scroller: ScrollViewProxy, id: String, delay: Double, animate: Bool) {
|
||||
func scroll_to_event(scroller: ScrollViewProxy, id: String, delay: Double, animate: Bool, anchor: UnitPoint = .bottom) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
|
||||
if animate {
|
||||
withAnimation {
|
||||
scroller.scrollTo(id, anchor: .bottom)
|
||||
scroller.scrollTo(id, anchor: anchor)
|
||||
}
|
||||
} else {
|
||||
scroller.scrollTo(id, anchor: .bottom)
|
||||
scroller.scrollTo(id, anchor: anchor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ struct EventBody: View {
|
||||
|
||||
let should_show_img = should_show_images(contacts: damus_state.contacts, ev: event, our_pubkey: damus_state.pubkey, booster_pubkey: nil)
|
||||
|
||||
NoteContentView(damus_state: damus_state, event: event, show_images: should_show_img, artifacts: .just_content(content), size: size)
|
||||
NoteContentView(damus_state: damus_state, event: event, show_images: should_show_img, size: size, artifacts: .just_content(content))
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ struct ReplyDescription: View {
|
||||
let profiles: Profiles
|
||||
|
||||
var body: some View {
|
||||
Text("\(reply_desc(profiles: profiles, event: event))")
|
||||
Text(verbatim: "\(reply_desc(profiles: profiles, event: event))")
|
||||
.font(.footnote)
|
||||
.foregroundColor(.gray)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
@@ -35,7 +35,7 @@ struct SelectedEventView: View {
|
||||
BuilderEventView(damus: damus, event_id: mention.ref.id)
|
||||
}
|
||||
|
||||
Text("\(format_date(event.created_at))")
|
||||
Text(verbatim: "\(format_date(event.created_at))")
|
||||
.padding(.top, 10)
|
||||
.font(.footnote)
|
||||
.foregroundColor(.gray)
|
||||
|
||||
@@ -33,7 +33,7 @@ struct TextEvent: View {
|
||||
HStack(alignment: .center) {
|
||||
EventProfileName(pubkey: pubkey, profile: profile, damus: damus, show_friend_confirmed: true, size: .normal)
|
||||
|
||||
Text("\(format_relative_time(event.created_at))")
|
||||
Text(verbatim: "\(format_relative_time(event.created_at))")
|
||||
.foregroundColor(.gray)
|
||||
|
||||
Spacer()
|
||||
|
||||
@@ -17,7 +17,7 @@ struct MentionView: View {
|
||||
let pk = bech32_pubkey(mention.ref.ref_id) ?? mention.ref.ref_id
|
||||
PubkeyView(pubkey: pk, relay: mention.ref.relay_id)
|
||||
case .event:
|
||||
Text("< e >", comment: "Placeholder for event mention.")
|
||||
Text(verbatim: "< e >")
|
||||
//EventBlockView(pubkey: mention.ref.ref_id, relay: mention.ref.relay_id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,37 +9,49 @@ import SwiftUI
|
||||
import LinkPresentation
|
||||
import NaturalLanguage
|
||||
|
||||
struct Blur: UIViewRepresentable {
|
||||
var style: UIBlurEffect.Style = .systemUltraThinMaterial
|
||||
|
||||
func makeUIView(context: Context) -> UIVisualEffectView {
|
||||
return UIVisualEffectView(effect: UIBlurEffect(style: style))
|
||||
}
|
||||
|
||||
func updateUIView(_ uiView: UIVisualEffectView, context: Context) {
|
||||
uiView.effect = UIBlurEffect(style: style)
|
||||
}
|
||||
}
|
||||
|
||||
struct NoteContentView: View {
|
||||
|
||||
let damus_state: DamusState
|
||||
let event: NostrEvent
|
||||
let show_images: Bool
|
||||
let size: EventViewKind
|
||||
|
||||
@State var artifacts: NoteArtifacts
|
||||
|
||||
let size: EventViewKind
|
||||
|
||||
@State var preview: LinkViewRepresentable? = nil
|
||||
|
||||
func MainContent() -> some View {
|
||||
return VStack(alignment: .leading) {
|
||||
Text(artifacts.content)
|
||||
.font(eventviewsize_to_font(size))
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
|
||||
|
||||
if size == .selected {
|
||||
TranslateView(damus_state: damus_state, event: event, size: size)
|
||||
SelectableText(attributedString: artifacts.content)
|
||||
TranslateView(damus_state: damus_state, event: event)
|
||||
} else {
|
||||
Text(artifacts.content)
|
||||
.font(eventviewsize_to_font(size))
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
|
||||
if show_images && artifacts.images.count > 0 {
|
||||
ImageCarousel(urls: artifacts.images)
|
||||
} else if !show_images && artifacts.images.count > 0 {
|
||||
ImageCarousel(urls: artifacts.images)
|
||||
.blur(radius: 10)
|
||||
.overlay {
|
||||
Rectangle()
|
||||
.opacity(0.50)
|
||||
}
|
||||
.cornerRadius(10)
|
||||
ZStack {
|
||||
ImageCarousel(urls: artifacts.images)
|
||||
Blur()
|
||||
.disabled(true)
|
||||
}
|
||||
.cornerRadius(10)
|
||||
}
|
||||
if artifacts.invoices.count > 0 {
|
||||
InvoicesView(our_pubkey: damus_state.keypair.pubkey, invoices: artifacts.invoices)
|
||||
@@ -155,7 +167,7 @@ struct NoteContentView_Previews: PreviewProvider {
|
||||
let state = test_damus_state()
|
||||
let content = "hi there ¯\\_(ツ)_/¯ https://jb55.com/s/Oct12-150217.png 5739a762ef6124dd.jpg"
|
||||
let artifacts = NoteArtifacts(content: AttributedString(stringLiteral: content), images: [], invoices: [], links: [])
|
||||
NoteContentView(damus_state: state, event: NostrEvent(content: content, pubkey: "pk"), show_images: true, artifacts: artifacts, size: .normal)
|
||||
NoteContentView(damus_state: state, event: NostrEvent(content: content, pubkey: "pk"), show_images: true, size: .normal, artifacts: artifacts)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -80,28 +80,41 @@ struct PostView: View {
|
||||
self.send_post()
|
||||
}
|
||||
}
|
||||
.font(.system(size: 14, weight: .bold))
|
||||
.frame(width: 80, height: 30)
|
||||
.foregroundColor(.white)
|
||||
.background(LINEAR_GRADIENT)
|
||||
.clipShape(Capsule())
|
||||
}
|
||||
}
|
||||
.padding([.top, .bottom], 4)
|
||||
|
||||
ZStack(alignment: .topLeading) {
|
||||
TextEditor(text: $post)
|
||||
.focused($focus)
|
||||
.textInputAutocapitalization(.sentences)
|
||||
.onChange(of: post) { _ in
|
||||
if let replying_to {
|
||||
damus_state.drafts.replies[replying_to] = post
|
||||
} else {
|
||||
damus_state.drafts.post = post
|
||||
|
||||
HStack(alignment: .top) {
|
||||
|
||||
ProfilePicView(pubkey: damus_state.pubkey, size: 45.0, highlight: .none, profiles: damus_state.profiles)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
ZStack(alignment: .topLeading) {
|
||||
|
||||
TextEditor(text: $post)
|
||||
.focused($focus)
|
||||
.textInputAutocapitalization(.sentences)
|
||||
.onChange(of: post) { _ in
|
||||
if let replying_to {
|
||||
damus_state.drafts.replies[replying_to] = post
|
||||
} else {
|
||||
damus_state.drafts.post = post
|
||||
}
|
||||
}
|
||||
|
||||
if post.isEmpty {
|
||||
Text(POST_PLACEHOLDER)
|
||||
.padding(.top, 8)
|
||||
.padding(.leading, 4)
|
||||
.foregroundColor(Color(uiColor: .placeholderText))
|
||||
.allowsHitTesting(false)
|
||||
}
|
||||
}
|
||||
|
||||
if post.isEmpty {
|
||||
Text(POST_PLACEHOLDER)
|
||||
.padding(.top, 8)
|
||||
.padding(.leading, 4)
|
||||
.foregroundColor(Color(uiColor: .placeholderText))
|
||||
.allowsHitTesting(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,3 +181,9 @@ func get_searching_string(_ post: String) -> String? {
|
||||
|
||||
return String(last_word.dropFirst())
|
||||
}
|
||||
|
||||
struct PostView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
PostView(replying_to: nil, references: [], damus_state: test_damus_state())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import SwiftUI
|
||||
func PowView(_ mpow: Int?) -> some View
|
||||
{
|
||||
let pow = mpow ?? 0
|
||||
return Text("\(pow)")
|
||||
return Text(verbatim: "\(pow)")
|
||||
.font(.callout)
|
||||
.foregroundColor(calculate_pow_color(pow))
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ struct ProfileNameView: View {
|
||||
var body: some View {
|
||||
Group {
|
||||
if let real_name = profile?.display_name {
|
||||
VStack(alignment: .leading) {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Text(real_name)
|
||||
.font(.title3.weight(.bold))
|
||||
HStack(alignment: .center, spacing: spacing) {
|
||||
@@ -30,6 +30,7 @@ struct ProfileNameView: View {
|
||||
FollowsYou()
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
KeyView(pubkey: pubkey)
|
||||
.pubkey_context_menu(bech32_pubkey: pubkey)
|
||||
}
|
||||
|
||||
@@ -33,23 +33,12 @@ func pfp_line_width(_ h: Highlight) -> CGFloat {
|
||||
}
|
||||
|
||||
struct InnerProfilePicView: View {
|
||||
|
||||
let url: URL?
|
||||
let fallbackUrl: URL?
|
||||
let pubkey: String
|
||||
let size: CGFloat
|
||||
let highlight: Highlight
|
||||
|
||||
@ObservedObject var imageModel: KFImageModel
|
||||
|
||||
init(url: URL?, fallbackUrl: URL?, pubkey: String, size: CGFloat, highlight: Highlight) {
|
||||
self.pubkey = pubkey
|
||||
self.size = size
|
||||
self.highlight = highlight
|
||||
self.imageModel = KFImageModel(
|
||||
url: url,
|
||||
fallbackUrl: fallbackUrl,
|
||||
maxByteSize: 5_242_880, // 5Mib
|
||||
downsampleSize: CGSize(width: 200, height: 200)
|
||||
)
|
||||
}
|
||||
|
||||
var PlaceholderColor: Color {
|
||||
return id_to_color(pubkey)
|
||||
@@ -67,25 +56,16 @@ struct InnerProfilePicView: View {
|
||||
ZStack {
|
||||
Color(uiColor: .systemBackground)
|
||||
|
||||
KFAnimatedImage(imageModel.url)
|
||||
.callbackQueue(.dispatch(.global(qos: .background)))
|
||||
.processingQueue(.dispatch(.global(qos: .background)))
|
||||
.serialize(by: imageModel.serializer)
|
||||
.setProcessor(imageModel.processor)
|
||||
.cacheOriginalImage()
|
||||
KFAnimatedImage(url)
|
||||
.imageContext(.pfp)
|
||||
.onFailure(fallbackUrl: fallbackUrl, cacheKey: url?.absoluteString)
|
||||
.cancelOnDisappear(true)
|
||||
.configure { view in
|
||||
view.framePreloadCount = 1
|
||||
view.framePreloadCount = 3
|
||||
}
|
||||
.placeholder { _ in
|
||||
Placeholder
|
||||
}
|
||||
.scaleFactor(UIScreen.main.scale)
|
||||
.loadDiskFileSynchronously()
|
||||
.fade(duration: 0.1)
|
||||
.onFailure { _ in
|
||||
imageModel.downloadFailed()
|
||||
}
|
||||
.id(imageModel.refreshID)
|
||||
}
|
||||
.frame(width: size, height: size)
|
||||
.clipShape(Circle())
|
||||
|
||||
@@ -80,9 +80,24 @@ struct EditButton: View {
|
||||
}
|
||||
}
|
||||
|
||||
struct VisualEffectView: UIViewRepresentable {
|
||||
var effect: UIVisualEffect?
|
||||
|
||||
func makeUIView(context: UIViewRepresentableContext<Self>) -> UIVisualEffectView {
|
||||
UIVisualEffectView()
|
||||
}
|
||||
|
||||
func updateUIView(_ uiView: UIVisualEffectView, context: UIViewRepresentableContext<Self>) {
|
||||
uiView.effect = effect
|
||||
}
|
||||
}
|
||||
|
||||
struct ProfileView: View {
|
||||
let damus_state: DamusState
|
||||
let zoom_size: CGFloat = 350
|
||||
let pfp_size: CGFloat = 90.0
|
||||
let bannerHeight: CGFloat = 150.0
|
||||
|
||||
static let markdown = Markdown()
|
||||
|
||||
@State private var selected_tab: ProfileTab = .posts
|
||||
@StateObject var profile: ProfileModel
|
||||
@@ -92,20 +107,13 @@ struct ProfileView: View {
|
||||
@State var is_zoomed: Bool = false
|
||||
@State var show_share_sheet: Bool = false
|
||||
@State var action_sheet_presented: Bool = false
|
||||
@State var filter_state : FilterState = .posts
|
||||
@State var yOffset: CGFloat = 0
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
@Environment(\.openURL) var openURL
|
||||
|
||||
// We just want to have a white "< Home" text here, however,
|
||||
// setting the initialiser is causing issues, and it's late.
|
||||
// Ref: https://blog.techchee.com/navigation-bar-title-style-color-and-custom-back-button-in-swiftui/
|
||||
/*
|
||||
init(damus_state: DamusState, zoom_size: CGFloat = 350) {
|
||||
self.damus_state = damus_state
|
||||
self.zoom_size = zoom_size
|
||||
Theme.navigationBarColors(background: nil, titleColor: .white, tintColor: nil)
|
||||
}*/
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
|
||||
func fillColor() -> Color {
|
||||
colorScheme == .light ? Color("DamusLightGrey") : Color("DamusDarkGrey")
|
||||
@@ -115,7 +123,98 @@ struct ProfileView: View {
|
||||
colorScheme == .light ? Color("DamusWhite") : Color("DamusBlack")
|
||||
}
|
||||
|
||||
func LNButton(lnurl: String, profile: Profile) -> some View {
|
||||
func bannerBlurViewOpacity() -> Double {
|
||||
let progress = -(yOffset + navbarHeight) / 100
|
||||
return Double(-yOffset > navbarHeight ? progress : 0)
|
||||
}
|
||||
|
||||
var bannerSection: some View {
|
||||
GeometryReader { proxy -> AnyView in
|
||||
|
||||
let minY = proxy.frame(in: .global).minY
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.yOffset = minY
|
||||
}
|
||||
|
||||
return AnyView(
|
||||
VStack(spacing: 0) {
|
||||
ZStack {
|
||||
BannerImageView(pubkey: profile.pubkey, profiles: damus_state.profiles)
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(width: proxy.size.width, height: minY > 0 ? bannerHeight + minY : bannerHeight)
|
||||
.clipped()
|
||||
|
||||
VisualEffectView(effect: UIBlurEffect(style: .systemUltraThinMaterial)).opacity(bannerBlurViewOpacity())
|
||||
}
|
||||
|
||||
Divider().opacity(bannerBlurViewOpacity())
|
||||
}
|
||||
.frame(height: minY > 0 ? bannerHeight + minY : nil)
|
||||
.offset(y: minY > 0 ? -minY : -minY < navbarHeight ? 0 : -minY - navbarHeight)
|
||||
)
|
||||
|
||||
}
|
||||
.frame(height: bannerHeight)
|
||||
}
|
||||
|
||||
var navbarHeight: CGFloat {
|
||||
return 100.0 - (Theme.safeAreaInsets?.top ?? 0)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func navImage(systemImage: String) -> some View {
|
||||
Image(systemName: systemImage)
|
||||
.frame(width: 33, height: 33)
|
||||
.background(Color.black.opacity(0.6))
|
||||
.clipShape(Circle())
|
||||
}
|
||||
|
||||
var navBackButton: some View {
|
||||
Button {
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
} label: {
|
||||
navImage(systemImage: "chevron.left")
|
||||
}
|
||||
}
|
||||
|
||||
var navActionSheetButton: some View {
|
||||
Button(action: {
|
||||
action_sheet_presented = true
|
||||
}) {
|
||||
navImage(systemImage: "ellipsis")
|
||||
}
|
||||
.confirmationDialog(NSLocalizedString("Actions", comment: "Title for confirmation dialog to either share, report, or block a profile."), isPresented: $action_sheet_presented) {
|
||||
Button(NSLocalizedString("Share", comment: "Button to share the link to a profile.")) {
|
||||
show_share_sheet = true
|
||||
}
|
||||
|
||||
// Only allow reporting if logged in with private key and the currently viewed profile is not the logged in profile.
|
||||
if profile.pubkey != damus_state.pubkey && damus_state.is_privkey_user {
|
||||
Button(NSLocalizedString("Report", comment: "Button to report a profile."), role: .destructive) {
|
||||
let target: ReportTarget = .user(profile.pubkey)
|
||||
notify(.report, target)
|
||||
}
|
||||
|
||||
Button(NSLocalizedString("Block", comment: "Button to block a profile."), role: .destructive) {
|
||||
notify(.block, profile.pubkey)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var customNavbar: some View {
|
||||
HStack {
|
||||
navBackButton
|
||||
Spacer()
|
||||
navActionSheetButton
|
||||
}
|
||||
.padding(.top, 5)
|
||||
.padding(.horizontal)
|
||||
.accentColor(Color("DamusWhite"))
|
||||
}
|
||||
|
||||
func lnButton(lnurl: String, profile: Profile) -> some View {
|
||||
Button(action: {
|
||||
if damus_state.settings.show_wallet_selector {
|
||||
showing_select_wallet = true
|
||||
@@ -139,46 +238,8 @@ struct ProfileView: View {
|
||||
SelectWalletView(showingSelectWallet: $showing_select_wallet, our_pubkey: damus_state.pubkey, invoice: lnurl)
|
||||
}
|
||||
}
|
||||
|
||||
static let markdown = Markdown()
|
||||
|
||||
var ActionSheetButton: some View {
|
||||
Button(action: {
|
||||
action_sheet_presented = true
|
||||
}) {
|
||||
Image(systemName: "ellipsis.circle")
|
||||
.profile_button_style(scheme: colorScheme)
|
||||
}
|
||||
.confirmationDialog(NSLocalizedString("Actions", comment: "Title for confirmation dialog to either share, report, or block a profile."), isPresented: $action_sheet_presented) {
|
||||
Button(NSLocalizedString("Share", comment: "Button to share the link to a profile.")) {
|
||||
show_share_sheet = true
|
||||
}
|
||||
|
||||
// Only allow reporting if logged in with private key and the currently viewed profile is not the logged in profile.
|
||||
if profile.pubkey != damus_state.pubkey && damus_state.is_privkey_user {
|
||||
Button(NSLocalizedString("Report", comment: "Button to report a profile."), role: .destructive) {
|
||||
let target: ReportTarget = .user(profile.pubkey)
|
||||
notify(.report, target)
|
||||
}
|
||||
|
||||
Button(NSLocalizedString("Block", comment: "Button to block a profile."), role: .destructive) {
|
||||
notify(.block, profile.pubkey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var ShareButton: some View {
|
||||
Button(action: {
|
||||
show_share_sheet = true
|
||||
}) {
|
||||
Image(systemName: "square.and.arrow.up.circle")
|
||||
.profile_button_style(scheme: colorScheme)
|
||||
}
|
||||
}
|
||||
|
||||
var DMButton: some View {
|
||||
var dmButton: some View {
|
||||
let dm_model = damus_state.dms.lookup_or_create(profile.pubkey)
|
||||
let dmview = DMChatView(damus_state: damus_state, pubkey: profile.pubkey)
|
||||
.environmentObject(dm_model)
|
||||
@@ -187,44 +248,17 @@ struct ProfileView: View {
|
||||
.profile_button_style(scheme: colorScheme)
|
||||
}
|
||||
}
|
||||
|
||||
private func getScrollOffset(_ geometry: GeometryProxy) -> CGFloat {
|
||||
geometry.frame(in: .global).minY
|
||||
}
|
||||
|
||||
private func getHeightForHeaderImage(_ geometry: GeometryProxy) -> CGFloat {
|
||||
let offset = getScrollOffset(geometry)
|
||||
let imageHeight = 150.0
|
||||
|
||||
if offset > 0 {
|
||||
return imageHeight + offset
|
||||
}
|
||||
|
||||
return imageHeight
|
||||
}
|
||||
|
||||
private func getOffsetForHeaderImage(_ geometry: GeometryProxy) -> CGFloat {
|
||||
let offset = getScrollOffset(geometry)
|
||||
|
||||
// Image was pulled down
|
||||
if offset > 0 {
|
||||
return -offset
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func ActionSection(profile_data: Profile?) -> some View {
|
||||
func actionSection(profile_data: Profile?) -> some View {
|
||||
return Group {
|
||||
ActionSheetButton
|
||||
|
||||
if let profile = profile_data {
|
||||
if let lnurl = profile.lnurl, lnurl != "" {
|
||||
LNButton(lnurl: lnurl, profile: profile)
|
||||
lnButton(lnurl: lnurl, profile: profile)
|
||||
}
|
||||
}
|
||||
|
||||
DMButton
|
||||
dmButton
|
||||
|
||||
if profile.pubkey != damus_state.pubkey {
|
||||
FollowButtonView(
|
||||
@@ -241,110 +275,42 @@ struct ProfileView: View {
|
||||
}
|
||||
}
|
||||
|
||||
func NameSection(profile_data: Profile?) -> some View {
|
||||
func pfpOffset() -> CGFloat {
|
||||
let progress = -yOffset / navbarHeight
|
||||
let offset = (pfp_size / 4.0) * (progress < 1.0 ? progress : 1)
|
||||
return offset > 0 ? offset : 0
|
||||
}
|
||||
|
||||
func pfpScale() -> CGFloat {
|
||||
let progress = -yOffset / navbarHeight
|
||||
let scale = 1.0 - (0.5 * (progress < 1.0 ? progress : 1))
|
||||
return scale < 1 ? scale : 1
|
||||
}
|
||||
|
||||
func nameSection(profile_data: Profile?) -> some View {
|
||||
return Group {
|
||||
HStack(alignment: .center) {
|
||||
ProfilePicView(pubkey: profile.pubkey, size: pfp_size, highlight: .custom(imageBorderColor(), 4.0), profiles: damus_state.profiles)
|
||||
.padding(.top, -(pfp_size / 2.0))
|
||||
.offset(y: pfpOffset())
|
||||
.scaleEffect(pfpScale())
|
||||
.onTapGesture {
|
||||
is_zoomed.toggle()
|
||||
}
|
||||
.fullScreenCover(isPresented: $is_zoomed) {
|
||||
ProfileZoomView(pubkey: profile.pubkey, profiles: damus_state.profiles) }
|
||||
.offset(y: -(pfp_size/2.0)) // Increase if set a frame
|
||||
|
||||
Spacer()
|
||||
|
||||
ActionSection(profile_data: profile_data)
|
||||
.offset(y: -15.0) // Increase if set a frame
|
||||
actionSection(profile_data: profile_data)
|
||||
}
|
||||
|
||||
let follows_you = profile.follows(pubkey: damus_state.pubkey)
|
||||
ProfileNameView(pubkey: profile.pubkey, profile: profile_data, follows_you: follows_you, damus: damus_state)
|
||||
//.padding(.bottom)
|
||||
.padding(.top,-(pfp_size/2.0))
|
||||
}
|
||||
}
|
||||
|
||||
var pfp_size: CGFloat {
|
||||
return 90.0
|
||||
}
|
||||
|
||||
var TopSection: some View {
|
||||
ZStack(alignment: .top) {
|
||||
GeometryReader { geometry in
|
||||
BannerImageView(pubkey: profile.pubkey, profiles: damus_state.profiles)
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(width: geometry.size.width, height: self.getHeightForHeaderImage(geometry))
|
||||
.clipped()
|
||||
.offset(x: 0, y: self.getOffsetForHeaderImage(geometry))
|
||||
|
||||
}.frame(height: BANNER_HEIGHT)
|
||||
|
||||
VStack(alignment: .leading, spacing: 8.0) {
|
||||
let profile_data = damus_state.profiles.lookup(id: profile.pubkey)
|
||||
|
||||
NameSection(profile_data: profile_data)
|
||||
|
||||
Text(ProfileView.markdown.process(profile_data?.about ?? ""))
|
||||
.font(.subheadline).textSelection(.enabled)
|
||||
|
||||
if let url = profile_data?.website_url {
|
||||
WebsiteLink(url: url)
|
||||
}
|
||||
|
||||
Divider()
|
||||
|
||||
HStack {
|
||||
if let contact = profile.contacts {
|
||||
let contacts = contact.referenced_pubkeys.map { $0.ref_id }
|
||||
let following_model = FollowingModel(damus_state: damus_state, contacts: contacts)
|
||||
NavigationLink(destination: FollowingView(damus_state: damus_state, following: following_model, whos: profile.pubkey)) {
|
||||
HStack {
|
||||
Text("\(Text("\(profile.following)", comment: "Number of profiles a user is following.").font(.subheadline.weight(.medium))) \(Text("Following", comment: "Part of a larger sentence to describe how many profiles a user is following.").font(.subheadline).foregroundColor(.gray))", comment: "Sentence composed of 2 variables to describe how many profiles a user is following. In source English, the first variable is the number of profiles being followed, and the second variable is 'Following'.")
|
||||
}
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
}
|
||||
let fview = FollowersView(damus_state: damus_state, whos: profile.pubkey)
|
||||
.environmentObject(followers)
|
||||
if followers.contacts != nil {
|
||||
NavigationLink(destination: fview) {
|
||||
FollowersCount
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
} else {
|
||||
FollowersCount
|
||||
.onTapGesture {
|
||||
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
||||
followers.contacts = []
|
||||
followers.subscribe()
|
||||
}
|
||||
}
|
||||
|
||||
if let relays = profile.relays {
|
||||
// Only open relay config view if the user is logged in with private key and they are looking at their own profile.
|
||||
let relay_text = Text("\(Text("\(relays.keys.count)", comment: "Number of relay servers a user is connected.").font(.subheadline.weight(.medium))) \(Text(String(format: NSLocalizedString("relays_count", comment: "Part of a larger sentence to describe how many relay servers a user is connected."), relays.keys.count)).font(.subheadline).foregroundColor(.gray))", comment: "Sentence composed of 2 variables to describe how many relay servers a user is connected. In source English, the first variable is the number of relay servers, and the second variable is 'Relay' or 'Relays'.")
|
||||
if profile.pubkey == damus_state.pubkey && damus_state.is_privkey_user {
|
||||
NavigationLink(destination: RelayConfigView(state: damus_state)) {
|
||||
relay_text
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
} else {
|
||||
NavigationLink(destination: UserRelaysView(state: damus_state, pubkey: profile.pubkey, relays: Array(relays.keys).sorted())) {
|
||||
relay_text
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal,18)
|
||||
//.offset(y:120)
|
||||
.padding(.top,150)
|
||||
}
|
||||
}
|
||||
|
||||
var FollowersCount: some View {
|
||||
var followersCount: some View {
|
||||
HStack {
|
||||
if followers.count == nil {
|
||||
Image(systemName: "square.and.arrow.down")
|
||||
@@ -353,24 +319,105 @@ struct ProfileView: View {
|
||||
.foregroundColor(.gray)
|
||||
} else {
|
||||
let followerCount = followers.count!
|
||||
Text("\(Text("\(followerCount)", comment: "Number of people following a user.").font(.subheadline.weight(.medium))) \(Text(String(format: NSLocalizedString("followers_count", comment: "Part of a larger sentence to describe how many people are following a user."), followerCount)).font(.subheadline).foregroundColor(.gray))", comment: "Sentence composed of 2 variables to describe how many people are following a user. In source English, the first variable is the number of followers, and the second variable is 'Follower' or 'Followers'.")
|
||||
Text("\(Text(verbatim: "\(followerCount)").font(.subheadline.weight(.medium))) \(Text(String(format: NSLocalizedString("followers_count", comment: "Part of a larger sentence to describe how many people are following a user."), followerCount)).font(.subheadline).foregroundColor(.gray))", comment: "Sentence composed of 2 variables to describe how many people are following a user. In source English, the first variable is the number of followers, and the second variable is 'Follower' or 'Followers'.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var aboutSection: some View {
|
||||
VStack(alignment: .leading, spacing: 8.0) {
|
||||
let profile_data = damus_state.profiles.lookup(id: profile.pubkey)
|
||||
|
||||
nameSection(profile_data: profile_data)
|
||||
|
||||
Text(ProfileView.markdown.process(profile_data?.about ?? ""))
|
||||
.font(.subheadline).textSelection(.enabled)
|
||||
|
||||
if let url = profile_data?.website_url {
|
||||
WebsiteLink(url: url)
|
||||
}
|
||||
|
||||
HStack {
|
||||
if let contact = profile.contacts {
|
||||
let contacts = contact.referenced_pubkeys.map { $0.ref_id }
|
||||
let following_model = FollowingModel(damus_state: damus_state, contacts: contacts)
|
||||
NavigationLink(destination: FollowingView(damus_state: damus_state, following: following_model, whos: profile.pubkey)) {
|
||||
HStack {
|
||||
Text("\(Text(verbatim: "\(profile.following)").font(.subheadline.weight(.medium))) \(Text("Following", comment: "Part of a larger sentence to describe how many profiles a user is following.").font(.subheadline).foregroundColor(.gray))", comment: "Sentence composed of 2 variables to describe how many profiles a user is following. In source English, the first variable is the number of profiles being followed, and the second variable is 'Following'.")
|
||||
}
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
}
|
||||
let fview = FollowersView(damus_state: damus_state, whos: profile.pubkey)
|
||||
.environmentObject(followers)
|
||||
if followers.contacts != nil {
|
||||
NavigationLink(destination: fview) {
|
||||
followersCount
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
} else {
|
||||
followersCount
|
||||
.onTapGesture {
|
||||
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
||||
followers.contacts = []
|
||||
followers.subscribe()
|
||||
}
|
||||
}
|
||||
|
||||
if let relays = profile.relays {
|
||||
// Only open relay config view if the user is logged in with private key and they are looking at their own profile.
|
||||
let relay_text = Text("\(Text(verbatim: "\(relays.keys.count)").font(.subheadline.weight(.medium))) \(Text(String(format: NSLocalizedString("relays_count", comment: "Part of a larger sentence to describe how many relay servers a user is connected."), relays.keys.count)).font(.subheadline).foregroundColor(.gray))", comment: "Sentence composed of 2 variables to describe how many relay servers a user is connected. In source English, the first variable is the number of relay servers, and the second variable is 'Relay' or 'Relays'.")
|
||||
if profile.pubkey == damus_state.pubkey && damus_state.is_privkey_user {
|
||||
NavigationLink(destination: RelayConfigView(state: damus_state)) {
|
||||
relay_text
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
} else {
|
||||
NavigationLink(destination: UserRelaysView(state: damus_state, pubkey: profile.pubkey, relays: Array(relays.keys).sorted())) {
|
||||
relay_text
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
ScrollView {
|
||||
TopSection
|
||||
|
||||
Divider()
|
||||
ScrollView(.vertical) {
|
||||
VStack(spacing: 0) {
|
||||
bannerSection
|
||||
.zIndex(1)
|
||||
|
||||
InnerTimelineView(events: $profile.events, damus: damus_state, show_friend_icon: false, filter: { _ in true })
|
||||
VStack() {
|
||||
aboutSection
|
||||
|
||||
VStack(spacing: 0) {
|
||||
CustomPicker(selection: $filter_state, content: {
|
||||
Text("Posts", comment: "Label for filter for seeing only your posts (instead of posts and replies).").tag(FilterState.posts)
|
||||
Text("Posts & Replies", comment: "Label for filter for seeing your posts and replies (instead of only your posts).").tag(FilterState.posts_and_replies)
|
||||
})
|
||||
Divider()
|
||||
.frame(height: 1)
|
||||
}
|
||||
.background(colorScheme == .dark ? Color.black : Color.white)
|
||||
|
||||
if filter_state == FilterState.posts {
|
||||
InnerTimelineView(events: $profile.events, damus: damus_state, show_friend_icon: false, filter: FilterState.posts.filter)
|
||||
}
|
||||
if filter_state == FilterState.posts_and_replies {
|
||||
InnerTimelineView(events: $profile.events, damus: damus_state, show_friend_icon: false, filter: FilterState.posts_and_replies.filter)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, Theme.safeAreaInsets?.left)
|
||||
.zIndex(-yOffset > navbarHeight ? 0 : 1)
|
||||
}
|
||||
.frame(maxHeight: .infinity, alignment: .topLeading)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .topLeading)
|
||||
.ignoresSafeArea()
|
||||
.navigationTitle("")
|
||||
.navigationBarHidden(true)
|
||||
.overlay(customNavbar, alignment: .top)
|
||||
.onReceive(handle_notify(.switched_timeline)) { _ in
|
||||
dismiss()
|
||||
}
|
||||
@@ -390,7 +437,6 @@ struct ProfileView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.ignoresSafeArea()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -403,7 +449,6 @@ struct ProfileView_Previews: PreviewProvider {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func test_damus_state() -> DamusState {
|
||||
let pubkey = "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"
|
||||
let damus = DamusState.empty
|
||||
@@ -429,22 +474,30 @@ struct KeyView: View {
|
||||
colorScheme == .light ? Color("DamusBlack") : Color("DamusWhite")
|
||||
}
|
||||
|
||||
private func copyPubkey(_ pubkey: String) {
|
||||
UIPasteboard.general.string = pubkey
|
||||
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
||||
withAnimation {
|
||||
isCopied = true
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
|
||||
withAnimation {
|
||||
isCopied = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
let bech32 = bech32_pubkey(pubkey) ?? pubkey
|
||||
|
||||
HStack {
|
||||
RoundedRectangle(cornerRadius: 24)
|
||||
.frame(width: 275, height:22)
|
||||
RoundedRectangle(cornerRadius: 11)
|
||||
.frame(height: 22)
|
||||
.foregroundColor(fillColor())
|
||||
.overlay(
|
||||
HStack {
|
||||
Button {
|
||||
UIPasteboard.general.string = bech32
|
||||
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
||||
isCopied = true
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
|
||||
isCopied = false
|
||||
}
|
||||
copyPubkey(bech32)
|
||||
} label: {
|
||||
Label(NSLocalizedString("Public Key", comment: "Label indicating that the text is a user's public account key."), systemImage: "key.fill")
|
||||
.font(.custom("key", size: 12.0))
|
||||
@@ -456,23 +509,18 @@ struct KeyView: View {
|
||||
Text(abbrev_pubkey(bech32, amount: 16))
|
||||
.font(.footnote)
|
||||
.foregroundColor(keyColor())
|
||||
.offset(x:-3) // Not sure why this is needed.
|
||||
}
|
||||
)
|
||||
if isCopied != true {
|
||||
Button {
|
||||
UIPasteboard.general.string = bech32
|
||||
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
||||
isCopied = true
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
|
||||
isCopied = false
|
||||
}
|
||||
copyPubkey(bech32)
|
||||
} label: {
|
||||
Label {
|
||||
Text("Public key", comment: "Label indicating that the text is a user's public account key.")
|
||||
} icon: {
|
||||
Image("ic-copy")
|
||||
Image(systemName: "square.on.square.dashed")
|
||||
.contentShape(Rectangle())
|
||||
.foregroundColor(.gray)
|
||||
.frame(width: 20, height: 20)
|
||||
}
|
||||
.labelStyle(IconOnlyLabelStyle())
|
||||
@@ -480,12 +528,13 @@ struct KeyView: View {
|
||||
}
|
||||
} else {
|
||||
HStack {
|
||||
Image("ic-tick")
|
||||
Image(systemName: "checkmark.circle")
|
||||
.frame(width: 20, height: 20)
|
||||
Text(NSLocalizedString("Copied", comment: "Label indicating that a user's key was copied."))
|
||||
.font(.footnote)
|
||||
.foregroundColor(Color("DamusGreen"))
|
||||
.layoutPriority(1)
|
||||
}
|
||||
.foregroundColor(Color("DamusGreen"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,84 +5,81 @@
|
||||
// Created by scoder1747 on 12/27/22.
|
||||
//
|
||||
import SwiftUI
|
||||
import Kingfisher
|
||||
|
||||
private struct ImageContainerView: View {
|
||||
|
||||
let url: URL?
|
||||
|
||||
@State private var image: UIImage?
|
||||
@State private var showShareSheet = false
|
||||
|
||||
private struct ImageHandler: ImageModifier {
|
||||
@Binding var handler: UIImage?
|
||||
|
||||
func modify(_ image: UIImage) -> UIImage {
|
||||
handler = image
|
||||
return image
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
|
||||
KFAnimatedImage(url)
|
||||
.imageContext(.pfp)
|
||||
.configure { view in
|
||||
view.framePreloadCount = 3
|
||||
}
|
||||
.imageModifier(ImageHandler(handler: $image))
|
||||
.clipShape(Circle())
|
||||
.modifier(ImageContextMenuModifier(url: url, image: image, showShareSheet: $showShareSheet))
|
||||
.sheet(isPresented: $showShareSheet) {
|
||||
ShareSheet(activityItems: [url])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ProfileZoomView: View {
|
||||
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
|
||||
let pubkey: String
|
||||
let profiles: Profiles
|
||||
|
||||
@GestureState private var scaleState: CGFloat = 1
|
||||
@GestureState private var offsetState = CGSize.zero
|
||||
|
||||
@State private var offset = CGSize.zero
|
||||
@State private var scale: CGFloat = 1
|
||||
|
||||
func resetStatus(){
|
||||
self.offset = CGSize.zero
|
||||
self.scale = 1
|
||||
}
|
||||
|
||||
var zoomGesture: some Gesture {
|
||||
MagnificationGesture()
|
||||
.updating($scaleState) { currentState, gestureState, _ in
|
||||
gestureState = currentState
|
||||
}
|
||||
.onEnded { value in
|
||||
scale *= value
|
||||
}
|
||||
}
|
||||
|
||||
var dragGesture: some Gesture {
|
||||
DragGesture()
|
||||
.updating($offsetState) { currentState, gestureState, _ in
|
||||
gestureState = currentState.translation
|
||||
}.onEnded { value in
|
||||
offset.height += value.translation.height
|
||||
offset.width += value.translation.width
|
||||
}
|
||||
}
|
||||
|
||||
var doubleTapGesture : some Gesture {
|
||||
TapGesture(count: 2).onEnded { value in
|
||||
resetStatus()
|
||||
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
|
||||
var navBarView: some View {
|
||||
HStack {
|
||||
Button(action: {
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}, label: {
|
||||
Image(systemName: "xmark")
|
||||
.frame(width: 33, height: 33)
|
||||
.background(.regularMaterial)
|
||||
.clipShape(Circle())
|
||||
})
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack(alignment: .topLeading) {
|
||||
Color("DamusDarkGrey") // Or Color("DamusBlack")
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
ZStack {
|
||||
Color(.systemBackground)
|
||||
.ignoresSafeArea()
|
||||
|
||||
Button {
|
||||
ZoomableScrollView {
|
||||
ImageContainerView(url: get_profile_url(picture: nil, pubkey: pubkey, profiles: profiles))
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.padding(.top, Theme.safeAreaInsets?.top)
|
||||
.padding(.bottom, Theme.safeAreaInsets?.bottom)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
.ignoresSafeArea()
|
||||
.modifier(SwipeToDismissModifier(minDistance: 50, onDismiss: {
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
} label: {
|
||||
Image(systemName: "xmark")
|
||||
.foregroundColor(.white)
|
||||
.font(.subheadline)
|
||||
.padding(.leading, 20)
|
||||
}
|
||||
.zIndex(1)
|
||||
|
||||
VStack(alignment: .center) {
|
||||
|
||||
Spacer()
|
||||
|
||||
ProfilePicView(pubkey: pubkey, size: 200.0, highlight: .none, profiles: profiles)
|
||||
.padding(100)
|
||||
.scaledToFit()
|
||||
.scaleEffect(self.scale * scaleState)
|
||||
.offset(x: offset.width + offsetState.width, y: offset.height + offsetState.height)
|
||||
.gesture(SimultaneousGesture(zoomGesture, dragGesture))
|
||||
.gesture(doubleTapGesture)
|
||||
.modifier(SwipeToDismissModifier(minDistance: nil, onDismiss: {
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}))
|
||||
|
||||
Spacer()
|
||||
|
||||
}
|
||||
}))
|
||||
}
|
||||
.overlay(navBarView, alignment: .top)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ struct PubkeyView: View {
|
||||
var body: some View {
|
||||
let color: Color = id_to_color(pubkey)
|
||||
ZStack {
|
||||
Text("\(abbrev_pubkey(pubkey))", comment: "Abbreviated version of a nostr public key.")
|
||||
Text(verbatim: "\(abbrev_pubkey(pubkey))")
|
||||
.foregroundColor(color)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ struct RelayPaidDetail: View {
|
||||
Button(action: {
|
||||
openURL(url)
|
||||
}, label: {
|
||||
Text("\(url)")
|
||||
Text(verbatim: "\(url)")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,9 +22,8 @@ struct RelayConfigView: View {
|
||||
|
||||
var recommended: [RelayDescriptor] {
|
||||
let rs: [RelayDescriptor] = []
|
||||
return BOOTSTRAP_RELAYS.reduce(into: rs) { (xs, x) in
|
||||
if let _ = state.pool.get_relay(x) {
|
||||
} else {
|
||||
return BOOTSTRAP_RELAYS.reduce(into: rs) { xs, x in
|
||||
if state.pool.get_relay(x) == nil {
|
||||
xs.append(RelayDescriptor(url: URL(string: x)!, info: .rw))
|
||||
}
|
||||
}
|
||||
@@ -48,6 +47,10 @@ struct RelayConfigView: View {
|
||||
relay = "wss://" + relay
|
||||
}
|
||||
|
||||
if relay.hasSuffix("/") {
|
||||
relay.removeLast();
|
||||
}
|
||||
|
||||
guard let url = URL(string: relay) else {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@ struct SearchHomeView: View {
|
||||
RoundedRectangle(cornerRadius: 8)
|
||||
.foregroundColor(.secondary.opacity(0.2))
|
||||
}
|
||||
//.padding()
|
||||
}
|
||||
|
||||
var GlobalContent: some View {
|
||||
@@ -74,7 +73,7 @@ struct SearchHomeView: View {
|
||||
VStack {
|
||||
MainContent
|
||||
}
|
||||
.safeAreaInset(edge: .top) {
|
||||
.safeAreaInset(edge: .top, spacing: 0) {
|
||||
VStack(spacing: 0) {
|
||||
SearchInput
|
||||
//.frame(maxWidth: 275)
|
||||
|
||||
@@ -79,7 +79,8 @@ struct SearchResultsView: View {
|
||||
case .none:
|
||||
Text("none", comment: "No search results.")
|
||||
}
|
||||
}.padding(.horizontal)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,20 +95,20 @@ struct SearchResultsView: View {
|
||||
return
|
||||
}
|
||||
|
||||
if let _ = hex_decode(new), new.count == 64 {
|
||||
if hex_decode(new) != nil, new.count == 64 {
|
||||
self.result = .hex(new)
|
||||
return
|
||||
}
|
||||
|
||||
if new.starts(with: "npub") {
|
||||
if let _ = try? bech32_decode(new) {
|
||||
if (try? bech32_decode(new)) != nil {
|
||||
self.result = .profile(new)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if new.starts(with: "note") {
|
||||
if let _ = try? bech32_decode(new) {
|
||||
if (try? bech32_decode(new)) != nil {
|
||||
self.result = .note(new)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ struct SearchView: View {
|
||||
var body: some View {
|
||||
TimelineView(events: $search.events, loading: $search.loading, damus: appstate, show_friend_icon: true, filter: { _ in true })
|
||||
.navigationBarTitle(describe_search(search.search))
|
||||
.padding([.leading, .trailing], 6)
|
||||
.onReceive(handle_notify(.switched_timeline)) { obj in
|
||||
dismiss()
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ struct InnerTimelineView: View {
|
||||
NavigationLink(destination: MaybeBuildThreadView, isActive: $navigating) {
|
||||
EmptyView()
|
||||
}
|
||||
LazyVStack {
|
||||
LazyVStack(spacing: 0) {
|
||||
if events.isEmpty {
|
||||
EmptyTimelineView()
|
||||
} else {
|
||||
@@ -44,11 +44,11 @@ struct InnerTimelineView: View {
|
||||
nav_target = ev
|
||||
navigating = true
|
||||
}
|
||||
.padding(.top, 10)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.padding(.top,10)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ struct TimelineView: View {
|
||||
guard let event = events.filter(self.filter).first else {
|
||||
return
|
||||
}
|
||||
scroll_to_event(scroller: scroller, id: event.id, delay: 0.0, animate: true)
|
||||
scroll_to_event(scroller: scroller, id: event.id, delay: 0.0, animate: true, anchor: .top)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
/* Blank space to separate profile picture from profile editor form. */
|
||||
" " = "61b6edf1108e6f396680a33b02486a70_tr";
|
||||
|
||||
/* Description of how the nip05 identifier would be used for verification. */
|
||||
"'%@' at '%@' will be used for verification" = "سيتم التحقق من '%@' @ '%@'";
|
||||
|
||||
@@ -13,14 +10,8 @@
|
||||
/* Navigation bar title for view that shows who a user is following. */
|
||||
"(who) following" = "(who) يتابع";
|
||||
|
||||
/* Prefix character to username. */
|
||||
"@" = "@";
|
||||
|
||||
/* Abbreviated version of a nostr public key. */
|
||||
"%@" = "%@";
|
||||
|
||||
/* Sentence composed of 2 variables to describe how many reposts. In source English, the first variable is the number of reposts, and the second variable is 'Repost' or 'Reposts'.
|
||||
Sentence composed of 2 variables to describe how many relay servers a user is connected. In source English, the first variable is the number of relay servers, and the second variable is 'Relay' or 'Relays'. */
|
||||
Sentence composed of 2 variables to describe how many people are following a user. In source English, the first variable is the number of followers, and the second variable is 'Follower' or 'Followers'. */
|
||||
"%@ %@" = "%@ %@";
|
||||
|
||||
/* Alert message that informs a user was blocked. */
|
||||
@@ -35,16 +26,9 @@ Sentence composed of 2 variables to describe how many relay servers a user is co
|
||||
/* Explanation of what can be done by users to earn money. There is a heading that precedes this explanation which is a variable to this string. */
|
||||
"%@. Tip your friend's posts and stack sats with Bitcoin⚡️, the native currency of the internet." = "%@. بسهولة مطلقة، أرسل و استقبل برقيات البتكوين ⚡️عملة الانترنت العالمية.";
|
||||
|
||||
/* Number of reposts.
|
||||
Number of relay servers a user is connected. */
|
||||
"%lld" = "%lld";
|
||||
|
||||
/* Fraction of how many of the user's relay servers that are operational. */
|
||||
"%lld/%lld" = "%lld/%lld";
|
||||
|
||||
/* Placeholder for event mention. */
|
||||
"< e >" = "< e >";
|
||||
|
||||
/* Text indicating the zap amount. i.e. number of satoshis that were tipped to a user */
|
||||
"⚡️ %@" = "⚡️ %@";
|
||||
|
||||
@@ -220,6 +204,9 @@ Number of relay servers a user is connected. */
|
||||
/* Button to pay a Lightning invoice with the user's default Lightning wallet. */
|
||||
"Default Wallet" = "المحفظة الافتراضية";
|
||||
|
||||
/* Section title for zap configuration */
|
||||
"Default Zap Amount in sats" = "قيمة الوميض الافتراضية";
|
||||
|
||||
/* Button for deleting the users account.
|
||||
Button to delete a relay server that the user connects to.
|
||||
Button to remove a user from their blocklist.
|
||||
@@ -269,9 +256,6 @@ Number of relay servers a user is connected. */
|
||||
/* Label indicating that the below text is the EULA, an acronym for End User License Agreement. */
|
||||
"EULA" = "اتفاقية الاستخدام";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"EventDetailView" = "EventDetailView";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Filter" = "تصفية";
|
||||
|
||||
@@ -285,11 +269,11 @@ Number of relay servers a user is connected. */
|
||||
"Follow me on nostr" = "تابعني على نوستر";
|
||||
|
||||
/* Label describing followers of a user. */
|
||||
"Followers" = "المتابعون";
|
||||
"Followers" = "المتابِعون";
|
||||
|
||||
/* Text to indicate that the button next to it is in a state that indicates that it is in the process of following a profile.
|
||||
Part of a larger sentence to describe how many profiles a user is following. */
|
||||
"Following" = "المتابَعين";
|
||||
"Following" = "المتابَعون";
|
||||
|
||||
/* Label to indicate that the user is in the process of following another user. */
|
||||
"Following..." = "يتابع...";
|
||||
@@ -445,10 +429,12 @@ Part of a larger sentence to describe how many profiles a user is following. */
|
||||
/* Text to indicate that what is being shown is a post from a user who has been blocked. */
|
||||
"Post from a user you've blocked" = "منشور لمستخدم محظور";
|
||||
|
||||
/* Label for filter for seeing only posts (instead of posts and replies). */
|
||||
/* Label for filter for seeing only posts (instead of posts and replies).
|
||||
Label for filter for seeing only your posts (instead of posts and replies). */
|
||||
"Posts" = "المنشورات";
|
||||
|
||||
/* Label for filter for seeing posts and replies (instead of only posts). */
|
||||
/* Label for filter for seeing posts and replies (instead of only posts).
|
||||
Label for filter for seeing your posts and replies (instead of only your posts). */
|
||||
"Posts & Replies" = "المنشورات والردود";
|
||||
|
||||
/* Heading indicating that this application keeps personally identifiable information private. A sentence describing what is done to keep data private comes after this heading. */
|
||||
@@ -657,9 +643,6 @@ Part of a larger sentence to describe how many profiles a user is following. */
|
||||
/* Text box prompt to ask user to type their post. */
|
||||
"Type your post here..." = "اكتب المنشور هنا...";
|
||||
|
||||
/* Non-breaking space character to fill in blank space next to event action button icons. */
|
||||
"u{00A0}" = "u{00A0}";
|
||||
|
||||
/* Button to unfollow a user. */
|
||||
"Unfollow" = "الغاء المتابعة";
|
||||
|
||||
@@ -685,7 +668,8 @@ Part of a larger sentence to describe how many profiles a user is following. */
|
||||
Label to prompt username entry. */
|
||||
"Username" = "اسم المستخدم";
|
||||
|
||||
/* Label to display relay software version. */
|
||||
/* Label to display relay software version.
|
||||
Section title for displaying the version number of the Damus app. */
|
||||
"Version" = "الاصدار";
|
||||
|
||||
/* Sidebar menu label for Wallet view. */
|
||||
|
||||
12
damus/cs.lproj/InfoPlist.strings
Normal file
@@ -0,0 +1,12 @@
|
||||
/* Bundle display name */
|
||||
"CFBundleDisplayName" = "Damus";
|
||||
|
||||
/* Bundle name */
|
||||
"CFBundleName" = "damus";
|
||||
|
||||
/* Privacy - Face ID Usage Description */
|
||||
"NSFaceIDUsageDescription" = "Místní ověření pro přístup k privátnímu klíči";
|
||||
|
||||
/* Privacy - Photo Library Additions Usage Description */
|
||||
"NSPhotoLibraryAddUsageDescription" = "Udělení přístupu Damusu k fotografiím vám umožní ukládat obrázky.";
|
||||
|
||||
723
damus/cs.lproj/Localizable.strings
Normal file
@@ -0,0 +1,723 @@
|
||||
/* Description of how the nip05 identifier would be used for verification. */
|
||||
"'%@' at '%@' will be used for verification" = "'%@' na '%@' se použije pro účely ověření";
|
||||
|
||||
/* Description of why the nip05 identifier is invalid. */
|
||||
"'%@' is an invalid NIP-05 identifier. It should look like an email." = "'%@' je neplatný identifikátor NIP-05. Měl by vypadat jako e-mail.";
|
||||
|
||||
/* Navigation bar title for view that shows who is following a user. */
|
||||
"(Profile.displayName(profile: profile, pubkey: whos))'s Followers" = "Uživatele (Profile.displayName(profile: profile, pubkey: whos)) sledují";
|
||||
|
||||
/* Navigation bar title for view that shows who a user is following. */
|
||||
"(who) following" = "(who) sleduje";
|
||||
|
||||
/* Sentence composed of 2 variables to describe how many reposts. In source English, the first variable is the number of reposts, and the second variable is 'Repost' or 'Reposts'.
|
||||
Sentence composed of 2 variables to describe how many people are following a user. In source English, the first variable is the number of followers, and the second variable is 'Follower' or 'Followers'. */
|
||||
"%@ %@" = "%@ %@";
|
||||
|
||||
/* Alert message that informs a user was blocked. */
|
||||
"%@ has been blocked" = "%@ byl zablokován";
|
||||
|
||||
/* Explanation of what is done to keep personally identifiable information private. There is a heading that precedes this explanation which is a variable to this string. */
|
||||
"%@. Creating an account doesn't require a phone number, email or name. Get started right away with zero friction." = "%@. Vytvoření účtu nevyžaduje telefonní číslo, e-mail ani jméno. Začněte ihned bez dřiny.";
|
||||
|
||||
/* Explanation of what is done to keep private data encrypted. There is a heading that precedes this explanation which is a variable to this string. */
|
||||
"%@. End-to-End encrypted private messaging. Keep Big Tech out of your DMs" = "%@. End-to-End šifrované soukromé zprávy. Udržujte Technologické Giganty mimo vaše soukromé zprávy.";
|
||||
|
||||
/* Explanation of what can be done by users to earn money. There is a heading that precedes this explanation which is a variable to this string. */
|
||||
"%@. Tip your friend's posts and stack sats with Bitcoin⚡️, the native currency of the internet." = "%@. Dávejte odměny na příspěvky svých přátel a štosujte saty na Bitcoinu⚡️, nativní měně internetu.";
|
||||
|
||||
/* Fraction of how many of the user's relay servers that are operational. */
|
||||
"%lld/%lld" = "%lld/%lld";
|
||||
|
||||
/* Text indicating the zap amount. i.e. number of satoshis that were tipped to a user */
|
||||
"⚡️ %@" = "⚡️ %@";
|
||||
|
||||
/* Label to prompt for about text entry for user to describe about themself. */
|
||||
"About" = "Informace";
|
||||
|
||||
/* Label for About Me section of user profile form. */
|
||||
"About Me" = "O mně";
|
||||
|
||||
/* Placeholder text for About Me description. */
|
||||
"Absolute Boss" = "Absolutní šéf";
|
||||
|
||||
/* Button to accept the end user license agreement before being allowed into the app. */
|
||||
"Accept" = "Přijmout";
|
||||
|
||||
/* Label to indicate the public ID of the account. */
|
||||
"Account ID" = "ID účtu";
|
||||
|
||||
/* Title for confirmation dialog to either share, report, or block a profile. */
|
||||
"Actions" = "Akce";
|
||||
|
||||
/* Button to add recommended relay server.
|
||||
Button to confirm adding user inputted relay. */
|
||||
"Add" = "Přidat";
|
||||
|
||||
/* Button label to re-add all original participants as profiles to reply to in a note */
|
||||
"Add all" = "Přidat všechny";
|
||||
|
||||
/* Label for section for adding a relay server. */
|
||||
"Add Relay" = "Přidat relé";
|
||||
|
||||
/* Label to display relay contact user. */
|
||||
"Admin" = "Administrátor";
|
||||
|
||||
/* Any amount of sats */
|
||||
"Any" = "Libovolná částka";
|
||||
|
||||
/* Prompt for optional entry of API Key to use translation server. */
|
||||
"API Key (optional)" = "Klíč API (volitelný)";
|
||||
|
||||
/* Prompt for required entry of API Key to use translation server. */
|
||||
"API Key (required)" = "Klíč API (volitelný)";
|
||||
|
||||
/* Alert message to ask if user wants to repost a post. */
|
||||
"Are you sure you want to repost this?" = "Opravdu to chcete přesdílet?";
|
||||
|
||||
/* Label for Banner Image section of user profile form. */
|
||||
"Banner Image" = "Obrázek pozadí";
|
||||
|
||||
/* Reminder to user that they should save their account information. */
|
||||
"Before we get started, you'll need to save your account info, otherwise you won't be able to login in the future if you ever uninstall Damus." = "Než začnete, musíte si uložit informace o svém účtu, jinak se v budoucnu nebudete moci přihlásit, pokud Damus někdy odinstalujete.";
|
||||
|
||||
/* Dropdown option label for Lightning wallet, Bitcoin Beach. */
|
||||
"Bitcoin Beach" = "Bitcoin Beach";
|
||||
|
||||
/* Label for Bitcoin Lightning Tips section of user profile form. */
|
||||
"Bitcoin Lightning Tips" = "Lightning Bitcoin Odměny ";
|
||||
|
||||
/* Dropdown option label for Lightning wallet, Blixt Wallet */
|
||||
"Blixt Wallet" = "Blixt Wallet";
|
||||
|
||||
/* Alert button to block a user.
|
||||
Button to block a profile.
|
||||
Context menu option for blocking users. */
|
||||
"Block" = "Zablokovat";
|
||||
|
||||
/* Alert message prompt to ask if a user should be blocked. */
|
||||
"Block %@?" = "Zablokovat %@?";
|
||||
|
||||
/* Title of alert for blocking a user. */
|
||||
"Block User" = "Zablokovaní uživatele";
|
||||
|
||||
/* Sidebar menu label for Profile view. */
|
||||
"Blocked" = "Blokováno";
|
||||
|
||||
/* Navigation title of view to see list of blocked users. */
|
||||
"Blocked Users" = "Zablokovaní uživatelé";
|
||||
|
||||
/* Dropdown option label for Lightning wallet, Blue Wallet. */
|
||||
"Blue Wallet" = "Blue Wallet";
|
||||
|
||||
/* Accessibility label for boosts button */
|
||||
"Boosts" = "Boosts";
|
||||
|
||||
/* Dropdown option label for Lightning wallet, Breez. */
|
||||
"Breez" = "Breez";
|
||||
|
||||
/* Context menu option for broadcasting the user's note to all of the user's connected relay servers. */
|
||||
"Broadcast" = "Převysílat";
|
||||
|
||||
/* Alert button to cancel out of alert for blocking a user.
|
||||
Button to cancel out of alert that creates a new mutelist.
|
||||
Button to cancel out of posting a note.
|
||||
Button to cancel out of reposting a post.
|
||||
Button to cancel out of view adding user inputted relay.
|
||||
Cancel deleting the user.
|
||||
Cancel out of logging out the user. */
|
||||
"Cancel" = "Zrušit";
|
||||
|
||||
/* Dropdown option label for Lightning wallet, Cash App. */
|
||||
"Cash App" = "Cash App";
|
||||
|
||||
/* Button for clearing cached data. */
|
||||
"Clear" = "Vyčistit";
|
||||
|
||||
/* Section title for clearing cached data. */
|
||||
"Clear Cache" = "Vyčistit mezipaměť";
|
||||
|
||||
/* Label to display relay contact information. */
|
||||
"Contact" = "Kontakt";
|
||||
|
||||
/* Label indicating that a user's key was copied. */
|
||||
"Copied" = "Zkopírováno";
|
||||
|
||||
/* Button to copy a relay server address. */
|
||||
"Copy" = "Kopírovat";
|
||||
|
||||
/* Context menu option for copying the ID of the account that created the note. */
|
||||
"Copy Account ID" = "Kopírovat ID účtu";
|
||||
|
||||
/* Context menu option to copy an image into clipboard.
|
||||
Context menu option to copy an image to clipboard. */
|
||||
"Copy Image" = "Zkopírovat obrázek";
|
||||
|
||||
/* Context menu option to copy the URL of an image into clipboard. */
|
||||
"Copy Image URL" = "Zkopírovat adresu obrázku";
|
||||
|
||||
/* Title of section for copying a Lightning invoice identifier. */
|
||||
"Copy invoice" = "Zkopírovat fakturu";
|
||||
|
||||
/* Context menu option for copying a user's Lightning URL. */
|
||||
"Copy LNURL" = "Zkopírovat LNURL";
|
||||
|
||||
/* Context menu option for copying the ID of the note. */
|
||||
"Copy Note ID" = "Zkopírovat ID poznámky";
|
||||
|
||||
/* Context menu option for copying the JSON text from the note. */
|
||||
"Copy Note JSON" = "Zkopírovat JSON poznámky";
|
||||
|
||||
/* Button to copy report ID. */
|
||||
"Copy Report ID" = "Zkopírovat ID hlášení";
|
||||
|
||||
/* Context menu option for copying the text from an note. */
|
||||
"Copy Text" = "Zkopírovat text";
|
||||
|
||||
/* Context menu option for copying the ID of the user who created the note. */
|
||||
"Copy User Pubkey" = "Zkopírovat adresu uživatele";
|
||||
|
||||
/* Alert message to indicate that the blocked user could not be found. */
|
||||
"Could not find user to block..." = "Nepodařilo se najít uživatele k zablokování...";
|
||||
|
||||
/* Button to create account. */
|
||||
"Create" = "Vytvořit";
|
||||
|
||||
/* Button to create an account. */
|
||||
"Create Account" = "Vytvořit účet";
|
||||
|
||||
/* Title of alert prompting the user to create a new mutelist. */
|
||||
"Create new mutelist" = "Vytvoření nového seznamu umlčených";
|
||||
|
||||
/* Example description about Bitcoin creator(s), Satoshi Nakamoto. */
|
||||
"Creator(s) of Bitcoin. Absolute legend." = "Zakladatel(é) Bitcoinu. Absolutní legenda.";
|
||||
|
||||
/* Dropdown option for selecting a custom translation server. */
|
||||
"Custom" = "Vlastní";
|
||||
|
||||
/* Name of the app, shown on the first screen when user is not logged in. */
|
||||
"Damus" = "Damus";
|
||||
|
||||
/* Dropdown option for selecting DeepL as the translation service. */
|
||||
"DeepL (Proprietary, Higher Accuracy)" = "DeepL (Vlastní, Největší presnost)";
|
||||
|
||||
/* Button to pay a Lightning invoice with the user's default Lightning wallet. */
|
||||
"Default Wallet" = "Výchozí peněženka";
|
||||
|
||||
/* Section title for zap configuration */
|
||||
"Default Zap Amount in sats" = "Výchozí množství Zapů v satech";
|
||||
|
||||
/* Button for deleting the users account.
|
||||
Button to delete a relay server that the user connects to.
|
||||
Button to remove a user from their blocklist.
|
||||
Section title for deleting the user */
|
||||
"Delete" = "Smazat";
|
||||
|
||||
/* Button to delete the user's account. */
|
||||
"Delete Account" = "Smazat účet";
|
||||
|
||||
/* Alert message to indicate this is a deleted account */
|
||||
"Deleted Account" = "Účet je smazán";
|
||||
|
||||
/* Label to display relay description. */
|
||||
"Description" = "Popis";
|
||||
|
||||
/* Button to dismiss a text field alert. */
|
||||
"Dismiss" = "Odmítnout";
|
||||
|
||||
/* Label to prompt display name entry. */
|
||||
"Display Name" = "Celé jméno";
|
||||
|
||||
/* Navigation title for DMs view, where DM is the English abbreviation for Direct Message.
|
||||
Navigation title for view of DMs, where DM is an English abbreviation for Direct Message. */
|
||||
"DMs" = "Soukromé zprávy";
|
||||
|
||||
/* Button to dismiss wallet selection view for paying Lightning invoice. */
|
||||
"Done" = "Hotovo";
|
||||
|
||||
/* Heading indicating that this application allows users to earn money. */
|
||||
"Earn Money" = "Získejte peníze";
|
||||
|
||||
/* Button to edit user's profile. */
|
||||
"Edit" = "Upravit";
|
||||
|
||||
/* Text indicating that the view is used for editing which participants are replied to in a note. */
|
||||
"Edit participants" = "Upravit účastníky";
|
||||
|
||||
/* Heading indicating that this application keeps private messaging end-to-end encrypted. */
|
||||
"Encrypted" = "Šifrované";
|
||||
|
||||
/* Prompt for user to enter an account key to login. */
|
||||
"Enter your account key to login:" = "Pro přihlášení zadejte svůj klíč k účtu:";
|
||||
|
||||
/* Error message indicating why saving keys failed. */
|
||||
"Error: %@" = "Error: %@";
|
||||
|
||||
/* Label indicating that the below text is the EULA, an acronym for End User License Agreement. */
|
||||
"EULA" = "EULA";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Filter" = "Filtr";
|
||||
|
||||
/* Button to follow a user. */
|
||||
"Follow" = "Sledujte";
|
||||
|
||||
/* Button to follow a user back. */
|
||||
"Follow Back" = "Sledovat také";
|
||||
|
||||
/* Text on QR code view to prompt viewer looking at screen to follow the user. */
|
||||
"Follow me on nostr" = "Sledujte mě na nostru";
|
||||
|
||||
/* Label describing followers of a user. */
|
||||
"Followers" = "Sledující";
|
||||
|
||||
/* Text to indicate that the button next to it is in a state that indicates that it is in the process of following a profile.
|
||||
Part of a larger sentence to describe how many profiles a user is following. */
|
||||
"Following" = "Sleduje";
|
||||
|
||||
/* Label to indicate that the user is in the process of following another user. */
|
||||
"Following..." = "Sleduje...";
|
||||
|
||||
/* Text to indicate that button next to it is in a state that will follow a profile when tapped. */
|
||||
"Follows" = "Sledující";
|
||||
|
||||
/* Text to indicate that a user is following your profile. */
|
||||
"Follows you" = "Sleduje vás";
|
||||
|
||||
/* Dropdown option for selecting Free plan for DeepL translation service. */
|
||||
"Free" = "Bezplatný";
|
||||
|
||||
/* Button to navigate to DeepL website to get a translation API key. */
|
||||
"Get API Key" = "Získat klíč API";
|
||||
|
||||
/* Navigation bar title for Global view where posts from all connected relay servers appear. */
|
||||
"Global" = "Globální";
|
||||
|
||||
/* Navigation link to go to post referenced by hex code. */
|
||||
"Goto post %@" = "Přejít na příspěvek %@";
|
||||
|
||||
/* Navigation link to go to profile. */
|
||||
"Goto profile %@" = "Přejít na profil %@";
|
||||
|
||||
/* Button to hide a post from a user who has been blocked. */
|
||||
"Hide" = "Skrýt";
|
||||
|
||||
/* Button to hide the DeepL translation API key.
|
||||
Button to hide the LibreTranslate server API key. */
|
||||
"Hide API Key" = "Skrýt klíč API";
|
||||
|
||||
/* Navigation bar title for Home view where posts and replies appear from those who the user is following. */
|
||||
"Home" = "Domů";
|
||||
|
||||
/* Placeholder example text for profile picture URL. */
|
||||
"https://example.com/pic.jpg" = "https://priklad.webu.cz/fotka.jpg";
|
||||
|
||||
/* Placeholder example text for website URL for user profile. */
|
||||
"https://jb55.com" = "https://jb55.com";
|
||||
|
||||
/* Button for user to report that the account or content has illegal content. */
|
||||
"Illegal content" = "Nelegální obsah";
|
||||
|
||||
/* Error message indicating that an invalid account key was entered for login. */
|
||||
"Invalid key" = "Neplatný klíč";
|
||||
|
||||
/* Button for user to report that the account or content has spam. */
|
||||
"It's spam" = "Jde o spam";
|
||||
|
||||
/* Placeholder example text for identifier used for NIP-05 verification. */
|
||||
"jb55@jb55.com" = "jb55@jb55.com";
|
||||
|
||||
/* Moves the post button to the left side of the screen */
|
||||
"Left Handed" = "Pro leváky";
|
||||
|
||||
/* Button to complete account creation and start using the app. */
|
||||
"Let's go!" = "Jdeme na to!";
|
||||
|
||||
/* Dropdown option for selecting LibreTranslate as the translation service. */
|
||||
"LibreTranslate (Open Source)" = "LibreTranslate (Open Source)";
|
||||
|
||||
/* Placeholder text for entry of Lightning Address or LNURL. */
|
||||
"Lightning Address or LNURL" = "Lightning adresa nebo LNURL";
|
||||
|
||||
/* Indicates that the view is for paying a Lightning invoice. */
|
||||
"Lightning Invoice" = "Lightning faktura";
|
||||
|
||||
/* Accessibility Label for Like button */
|
||||
"Like" = "To se mi líbí";
|
||||
|
||||
/* Dropdown option label for Lightning wallet, LNLink. */
|
||||
"LNLink" = "LNLink";
|
||||
|
||||
/* Face ID usage description shown when trying to access private key */
|
||||
"Local authentication to access private key" = "Biometrické ověření pro přístup k soukromému klíči";
|
||||
|
||||
/* Dropdown option label for system default for Lightning wallet. */
|
||||
"Local default" = "Predeterminada del sistema";
|
||||
|
||||
/* Button to log into account.
|
||||
Button to log into an account. */
|
||||
"Login" = "Přihlásit se";
|
||||
|
||||
/* Alert for logging out the user.
|
||||
Button for logging out the user.
|
||||
Button to close the alert that informs that the current account has been deleted. */
|
||||
"Logout" = "Odhlásit se";
|
||||
|
||||
/* Reminder message in alert to get customer to verify that their private security account key is saved saved before logging out. */
|
||||
"Make sure your nsec account key is saved before you logout or you will lose access to this account" = "Před odhlášením se ujistěte, jestli je váš nsec klíč účtu uložen. Jinak ztratíte přístup k tomuto účtu.";
|
||||
|
||||
/* Dropdown option label for Lightning wallet, Muun. */
|
||||
"Muun" = "Muun";
|
||||
|
||||
/* Label for NIP-05 Verification section of user profile form. */
|
||||
"NIP-05 Verification" = "Ověření NIP-05";
|
||||
|
||||
/* Button to cancel out of posting a note after being alerted that it looks like they might be posting a private key. */
|
||||
"No" = "Ne";
|
||||
|
||||
/* Alert message prompt that asks if the user wants to create a new block list, overwriting previous block lists. */
|
||||
"No block list found, create a new one? This will overwrite any previous block lists." = "Nebyl nalezen žádný seznam zablokovaných, chcete vytvořit nový? Tím se přepíší všechny předchozí seznamy zablokovaných.";
|
||||
|
||||
/* No search results. */
|
||||
"none" = "žádné";
|
||||
|
||||
/* Dropdown option for selecting no translation service. */
|
||||
"None" = "Žádné";
|
||||
|
||||
/* Alert user that they might be attempting to paste a private key and ask them to confirm. */
|
||||
"Note contains \"nsec1\" private key. Are you sure?" = "Poznámka obsahuje \"nsec1\" soukromý klíč. Jste si jisti?";
|
||||
|
||||
/* Indicates that there are no notes in the timeline to view. */
|
||||
"Nothing to see here. Check back later!" = "Zde není nic k vidění. Zkontrolujte později!";
|
||||
|
||||
/* Toolbar label for Notifications view. */
|
||||
"Notifications" = "Upozornění";
|
||||
|
||||
/* String indicating that a given timestamp just occurred */
|
||||
"now" = "nyní";
|
||||
|
||||
/* Prompt for user to enter in an account key to login. This text shows the characters the key could start with if it was a private key. */
|
||||
"nsec1..." = "nsec1...";
|
||||
|
||||
/* Button for user to report that the account or content has nudity or explicit content. */
|
||||
"Nudity or explicit content" = "Nahota nebo explicitní obsah";
|
||||
|
||||
/* Label indicating that a form input is optional. */
|
||||
"optional" = "volitelné";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Paid Relay" = "Placené Relé";
|
||||
|
||||
/* Button to pay a Lightning invoice. */
|
||||
"Pay" = "Zaplatit";
|
||||
|
||||
/* Navigation bar title for view to pay Lightning invoice. */
|
||||
"Pay the Lightning invoice" = "Zaplaťtě Lightning fakturu";
|
||||
|
||||
/* Alert for deleting the users account. */
|
||||
"Permanently Delete Account" = "Trvale odstranit účet";
|
||||
|
||||
/* Dropdown option label for Lightning wallet, Phoenix. */
|
||||
"Phoenix" = "Phoenix";
|
||||
|
||||
/* Prompt selection of DeepL subscription plan to perform machine translations on notes */
|
||||
"Plan" = "Plan";
|
||||
|
||||
/* Button to post a note. */
|
||||
"Post" = "Zveřejnit";
|
||||
|
||||
/* Text to indicate that what is being shown is a post from a user who has been blocked. */
|
||||
"Post from a user you've blocked" = "Příspěvek od zablokovaného uživatele";
|
||||
|
||||
/* Label for filter for seeing only posts (instead of posts and replies). */
|
||||
"Posts" = "Příspěvky";
|
||||
|
||||
/* Label for filter for seeing posts and replies (instead of only posts). */
|
||||
"Posts & Replies" = "Příspěvky a odpovědi";
|
||||
|
||||
/* Heading indicating that this application keeps personally identifiable information private. A sentence describing what is done to keep data private comes after this heading. */
|
||||
"Private" = "Soukromé";
|
||||
|
||||
/* Title of the secure field that holds the user's private key. */
|
||||
"Private Key" = "Soukromý klíč";
|
||||
|
||||
/* Dropdown option for selecting Pro plan for DeepL translation service. */
|
||||
"Pro" = "Pro";
|
||||
|
||||
/* Sidebar menu label for Profile view. */
|
||||
"Profile" = "Profil";
|
||||
|
||||
/* Label for Profile Picture section of user profile form. */
|
||||
"Profile Picture" = "Profilová fotka";
|
||||
|
||||
/* Section title for the user's public account ID. */
|
||||
"Public Account ID" = "Veřejné ID účtu";
|
||||
|
||||
/* Label indicating that the text is a user's public account key. */
|
||||
"Public key" = "Veřejný klíč";
|
||||
|
||||
/* Label indicating that the text is a user's public account key. */
|
||||
"Public Key" = "Veřejný klíč";
|
||||
|
||||
/* Prompt to ask user if the key they entered is a public key. */
|
||||
"Public Key?" = "Je to veřejný klíč?";
|
||||
|
||||
/* Navigation bar title for Reactions view. */
|
||||
"Reactions" = "Reacciones";
|
||||
|
||||
/* Section title for recommend relay servers that could be added as part of configuration */
|
||||
"Recommended Relays" = "Doporučené Relé";
|
||||
|
||||
/* Button to reject the end user license agreement, which disallows the user from being let into the app. */
|
||||
"Reject" = "Zamítnout";
|
||||
|
||||
/* Label to display relay address.
|
||||
Text field for relay server. Used for testing purposes. */
|
||||
"Relay" = "Relé";
|
||||
|
||||
/* Sidebar menu label for Relays view. */
|
||||
"Relays" = "Relé";
|
||||
|
||||
/* Description of what was done as a result of sending a report to relay servers. */
|
||||
"Relays have been notified and clients will be able to use this information to filter content. Thank you!" = "Relé byly informovány a klienti budou moci tyto informace použít k filtrování obsahu. Děkujeme!";
|
||||
|
||||
/* Button label to remove all participants from a note reply. */
|
||||
"Remove all" = "Odebrat všechny";
|
||||
|
||||
/* Accessibility label for reply button */
|
||||
"Reply" = "Odpovědět";
|
||||
|
||||
/* Label to indicate that the user is replying to themself. */
|
||||
"Reply to self" = "Odpovědět sobě";
|
||||
|
||||
/* Label to indicate that the user is replying to 2 users. */
|
||||
"Replying to %@ & %@" = "Reagovat na %1$@ a %2$@";
|
||||
|
||||
/* Indicating that the user is replying to the following listed people. */
|
||||
"Replying to:" = "Reagovat na:";
|
||||
|
||||
/* Button to report a profile.
|
||||
Context menu option for reporting content. */
|
||||
"Report" = "Nahlásit";
|
||||
|
||||
/* Label indicating that the text underneath is the identifier of the report that was sent to relay servers. */
|
||||
"Report ID:" = "ID nahlášení:";
|
||||
|
||||
/* Message indicating that a report was successfully sent to relay servers. */
|
||||
"Report sent!" = "Nahlášeno!";
|
||||
|
||||
/* Button to confirm reposting a post.
|
||||
Title of alert for confirming to repost a post. */
|
||||
"Repost" = "Přesdílet";
|
||||
|
||||
/* Text indicating that the post was reposted (i.e. re-shared). */
|
||||
"Reposted" = "Přesdíleno";
|
||||
|
||||
/* Navigation bar title for Reposts view. */
|
||||
"Reposts" = "Přesdílené";
|
||||
|
||||
/* Picker option for DM selector for seeing only message requests (DMs that someone else sent the user which has not been responded to yet). DM is the English abbreviation for Direct Message. */
|
||||
"Requests" = "Žádosti";
|
||||
|
||||
/* Button to retry completing account creation after an error occurred. */
|
||||
"Retry" = "Opakovat";
|
||||
|
||||
/* Dropdown option label for Lightning wallet, River */
|
||||
"River" = "River";
|
||||
|
||||
/* Example username of Bitcoin creator(s), Satoshi Nakamoto. */
|
||||
"satoshi" = "satoshi";
|
||||
|
||||
/* Name of Bitcoin creator(s). */
|
||||
"Satoshi Nakamoto" = "Satoshi Nakamoto";
|
||||
|
||||
/* Button for saving profile. */
|
||||
"Save" = "Uložit";
|
||||
|
||||
/* Context menu option to save an image. */
|
||||
"Save Image" = "Uložit obrázek";
|
||||
|
||||
/* Text on QR code view to prompt viewer to scan the QR code on screen with their device camera. */
|
||||
"Scan the code" = "Naskenovat kód";
|
||||
|
||||
/* Navigation link to search hashtag. */
|
||||
"Search hashtag: #%@" = "Hledat hashtag: #%@";
|
||||
|
||||
/* Placeholder text to prompt entry of search query. */
|
||||
"Search..." = "Hledat...";
|
||||
|
||||
/* Section title for user's secret account login key. */
|
||||
"Secret Account Login Key" = "Tajný klíč pro přihlášení k účtu";
|
||||
|
||||
/* Title of section for selecting a Lightning wallet to pay a Lightning invoice. */
|
||||
"Select a Lightning wallet" = "Vyberte Lightning peněženku ";
|
||||
|
||||
/* Prompt selection of user's default wallet */
|
||||
"Select default wallet" = "Vyberte výchozí peněženku";
|
||||
|
||||
/* Text prompt for user to send a message to the other user. */
|
||||
"Send a message to start the conversation..." = "Pošlete zprávu a zahajte konverzaci...";
|
||||
|
||||
/* Prompt selection of LibreTranslate server to perform machine translations on notes */
|
||||
"Server" = "Server";
|
||||
|
||||
/* Prompt selection of translation service provider. */
|
||||
"Service" = "Služba";
|
||||
|
||||
/* Navigation title for Settings view.
|
||||
Sidebar menu label for accessing the app settings */
|
||||
"Settings" = "Nastavení";
|
||||
|
||||
/* Button to share a post
|
||||
Button to share an image.
|
||||
Button to share the link to a profile. */
|
||||
"Share" = "Sdílet";
|
||||
|
||||
/* Button to show a post from a user who has been blocked.
|
||||
Toggle to show or hide user's secret account login key. */
|
||||
"Show" = "Zobrazit";
|
||||
|
||||
/* Button to show the DeepL translation API key.
|
||||
Button to show the LibreTranslate server API key. */
|
||||
"Show API Key" = "Zobrazit klíč API";
|
||||
|
||||
/* Toggle to show or hide selection of wallet. */
|
||||
"Show wallet selector" = "Zobrazit výběr peněženky";
|
||||
|
||||
/* Sidebar menu label to sign out of the account. */
|
||||
"Sign out" = "Odhlásit";
|
||||
|
||||
/* Label to display relay software. */
|
||||
"Software" = "Software";
|
||||
|
||||
/* Dropdown option label for Lightning wallet, Strike. */
|
||||
"Strike" = "Strike";
|
||||
|
||||
/* Label to display relay's supported NIPs. */
|
||||
"Supported NIPs" = "Podporované NIP-y ";
|
||||
|
||||
/* Button to close out of alert that informs that the action to block a user was successful. */
|
||||
"Thanks!" = "Děkuji!";
|
||||
|
||||
/* Button for user to report that the account is impersonating someone. */
|
||||
"They are impersonating someone" = "Za někoho se vydává";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"This is a paid relay, you must pay for posts to be accepted." = "Jedná se o placené Relé, musíte zaplatit za příspěvky, které mají být přijaty.";
|
||||
|
||||
/* Warning that the inputted account key is a public key and the result of what happens because of it. */
|
||||
"This is a public key, you will not be able to make posts or interact in any way. This is used for viewing accounts from their perspective." = " Jedná se o veřejný klíč, takže nebudete moci odesílat příspěvky ani nijak komunikovat. Slouží k zobrazení vašeho účtů z perspektivy sledujících.";
|
||||
|
||||
/* Warning that the inputted account key for login is an old-style and asking user to verify if it is a public key. */
|
||||
"This is an old-style nostr key. We're not sure if it's a pubkey or private key. Please toggle the button below if this a public key." = "Jedná se o nostr klíč starého typu. Nejsme si jisti, zda se jedná o veřejný nebo soukromý klíč. Pokud se jedná o veřejný klíč, přepněte prosím tlačítko níže.";
|
||||
|
||||
/* Label to describe that a public key is the user's account ID and what they can do with it. */
|
||||
"This is your account ID, you can give this to your friends so that they can follow you. Click to copy." = "Toto je vaše ID účtu, které můžete dát svým přátelům, aby vás mohli začít sledovat. Klikněte pro zkopírování.";
|
||||
|
||||
/* Label to describe that a private key is the user's secret account key and what they should do with it. */
|
||||
"This is your secret account key. You need this to access your account. Don't share this with anyone! Save it in a password manager and keep it safe!" = "Toto je vaše tajné heslo, které potřebujete pro přístup ke svému účtu. Nikomu ho nesdělujte! Uložte jej do správce hesel a chraňte jej!";
|
||||
|
||||
/* Navigation bar title for note thread. */
|
||||
"Thread" = "Vlákno";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"To filter your %@ feed, please choose applicable relays from the list below:" = "Chcete-li filtrovat %@ obsah, vyberte příslušné Relé z níže uvedeného seznamu:";
|
||||
|
||||
/* Button to translate note from different language. */
|
||||
"Translate Note" = "Přeložit poznámku";
|
||||
|
||||
/* Button to indicate that the note has been translated from a different language. */
|
||||
"Translated from (lang)" = "Přeloženo z jazyku (lang)";
|
||||
|
||||
/* Button to indicate that the note is in the process of being translated from a different language. */
|
||||
"Translating from (lang)..." = "Překládáno z jazyka (lang)...";
|
||||
|
||||
/* Section title for selecting the translation service. */
|
||||
"Translations" = "Překlady";
|
||||
|
||||
/* 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. */
|
||||
"Type DELETE to delete" = "Napište \"DELETE\" pro smazání";
|
||||
|
||||
/* Text box prompt to ask user to type their post. */
|
||||
"Type your post here..." = "Escribe tu publicación aquí...";
|
||||
|
||||
/* Button to unfollow a user. */
|
||||
"Unfollow" = "Zrušit sledování";
|
||||
|
||||
/* Text to indicate that the button next to it is in a state that indicates that it is in the process of unfollowing a profile. */
|
||||
"Unfollowing" = "Rušení sledování";
|
||||
|
||||
/* Label to indicate that the user is in the process of unfollowing another user. */
|
||||
"Unfollowing..." = "Rušení sledování...";
|
||||
|
||||
/* Text to indicate that the button next to it is in a state that will unfollow a profile when tapped. */
|
||||
"Unfollows" = "Nesleduje";
|
||||
|
||||
/* Example URL to LibreTranslate server */
|
||||
"URL" = "URL";
|
||||
|
||||
/* Alert message to indicate the user has been blocked */
|
||||
"User blocked" = "Uživatel zablokován";
|
||||
|
||||
/* Alert message that informs a user was blocked. */
|
||||
"User has been blocked" = "Uživatel byl zablokován";
|
||||
|
||||
/* Label for Username section of user profile form.
|
||||
Label to prompt username entry. */
|
||||
"Username" = "Uživatelské jméno";
|
||||
|
||||
/* Label to display relay software version.
|
||||
Section title for displaying the version number of the Damus app. */
|
||||
"Version" = "Verze";
|
||||
|
||||
/* Sidebar menu label for Wallet view. */
|
||||
"Wallet" = "Billetera";
|
||||
|
||||
/* Dropdown option label for Lightning wallet, Wallet of Satoshi. */
|
||||
"Wallet of Satoshi" = "Wallet of Satoshi";
|
||||
|
||||
/* Section title for selection of wallet. */
|
||||
"Wallet Selector" = "Výběr peněženek";
|
||||
|
||||
/* Label for Website section of user profile form. */
|
||||
"Website" = "Webová stránka";
|
||||
|
||||
/* Welcoming message to the reader. The variable is 'you', the reader. */
|
||||
"Welcome to the social network %@ control." = "Vítejte v sociální síti %@, kterou máte pod kontrolou.";
|
||||
|
||||
/* Text to welcome user. */
|
||||
"Welcome, %@!" = "Vítáme vás, %@!";
|
||||
|
||||
/* Header text to prompt user what issue they want to report. */
|
||||
"What do you want to report?" = "Co chcete nahlásit?";
|
||||
|
||||
/* Placeholder example for relay server address. */
|
||||
"wss://some.relay.com" = "wss://vlastni.rele.com";
|
||||
|
||||
/* Text of button that confirms to overwrite the existing mutelist. */
|
||||
"Yes, Overwrite" = "Ano, přepsat";
|
||||
|
||||
/* Button to proceed with posting a note even though it looks like they might be posting a private key. */
|
||||
"Yes, Post with Private Key" = "Ano, publikovat se soukromým klíčem";
|
||||
|
||||
/* You, in this context, is the person who controls their own social network. You is used in the context of a larger sentence that welcomes the reader to the social network that they control themself. */
|
||||
"you" = "tú";
|
||||
|
||||
/* Label for Your Name section of user profile form. */
|
||||
"Your Name" = "Vaše jméno";
|
||||
|
||||
/* Footer text to inform user what will happen when the report is submitted. */
|
||||
"Your report will be sent to the relays you are connected to" = "Nahlášení bude odesláno na všechny relé, ke kterým jste připojen.";
|
||||
|
||||
/* Accessibility label for zap button */
|
||||
"Zap" = "Zap";
|
||||
|
||||
/* Navigation bar title for the Zaps view. */
|
||||
"Zaps" = "Zapy";
|
||||
|
||||
/* Dropdown option label for Lightning wallet, Zebedee. */
|
||||
"Zebedee" = "Zebedee";
|
||||
|
||||
/* Dropdown option label for Lightning wallet, Zeus LN. */
|
||||
"Zeus LN" = "Zeus LN";
|
||||
|
||||
190
damus/cs.lproj/Localizable.stringsdict
Normal file
@@ -0,0 +1,190 @@
|
||||
<?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>NOTES</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>few</key>
|
||||
<string>%d other notes</string>
|
||||
<key>many</key>
|
||||
<string>%d other notes</string>
|
||||
<key>one</key>
|
||||
<string>%d jiná poznámka</string>
|
||||
<key>other</key>
|
||||
<string>%d jiné poznámky</string>
|
||||
</dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>··· %#@NOTES@ ···</string>
|
||||
</dict>
|
||||
<key>followers_count</key>
|
||||
<dict>
|
||||
<key>FOLLOWERS</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>few</key>
|
||||
<string>Followers</string>
|
||||
<key>many</key>
|
||||
<string>Followers</string>
|
||||
<key>one</key>
|
||||
<string>Seguidor</string>
|
||||
<key>other</key>
|
||||
<string>Sledují</string>
|
||||
</dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@FOLLOWERS@</string>
|
||||
</dict>
|
||||
<key>reactions_count</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>%#@REACTIONS@</string>
|
||||
<key>REACTIONS</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>few</key>
|
||||
<string>Reactions</string>
|
||||
<key>many</key>
|
||||
<string>Reactions</string>
|
||||
<key>one</key>
|
||||
<string>Reakce</string>
|
||||
<key>other</key>
|
||||
<string>Reakce</string>
|
||||
</dict>
|
||||
</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>few</key>
|
||||
<string>Relays</string>
|
||||
<key>many</key>
|
||||
<string>Relays</string>
|
||||
<key>one</key>
|
||||
<string>Relé</string>
|
||||
<key>other</key>
|
||||
<string>Relé</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>replying_to_one_and_others</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>Odpověď na %@%#@OTHERS@</string>
|
||||
<key>OTHERS</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>few</key>
|
||||
<string> & %d others</string>
|
||||
<key>many</key>
|
||||
<string> & %d others</string>
|
||||
<key>one</key>
|
||||
<string> a %d další</string>
|
||||
<key>other</key>
|
||||
<string> a %d další</string>
|
||||
<key>zero</key>
|
||||
<string></string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>replying_to_two_and_others</key>
|
||||
<dict>
|
||||
<key>NSStringLocalizedFormatKey</key>
|
||||
<string>Odpovědět na %@, %@%#@OTHERS@</string>
|
||||
<key>OTHERS</key>
|
||||
<dict>
|
||||
<key>NSStringFormatSpecTypeKey</key>
|
||||
<string>NSStringPluralRuleType</string>
|
||||
<key>NSStringFormatValueTypeKey</key>
|
||||
<string>d</string>
|
||||
<key>few</key>
|
||||
<string> & %d others</string>
|
||||
<key>many</key>
|
||||
<string> & %d others</string>
|
||||
<key>one</key>
|
||||
<string> a %d další</string>
|
||||
<key>other</key>
|
||||
<string> a %d další</string>
|
||||
<key>zero</key>
|
||||
<string></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>few</key>
|
||||
<string>Reposts</string>
|
||||
<key>many</key>
|
||||
<string>Reposts</string>
|
||||
<key>one</key>
|
||||
<string>Přesdílet</string>
|
||||
<key>other</key>
|
||||
<string>Přesdílené </string>
|
||||
</dict>
|
||||
</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>few</key>
|
||||
<string>%2$@ sats</string>
|
||||
<key>many</key>
|
||||
<string>%2$@ sats</string>
|
||||
<key>one</key>
|
||||
<string>%2$@ sat</string>
|
||||
<key>other</key>
|
||||
<string>%2$@ satů</string>
|
||||
</dict>
|
||||
</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>few</key>
|
||||
<string>Zaps</string>
|
||||
<key>many</key>
|
||||
<string>Zaps</string>
|
||||
<key>one</key>
|
||||
<string>Zap</string>
|
||||
<key>other</key>
|
||||
<string>Zapů</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -32,6 +32,7 @@ struct MainView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.dynamicTypeSize(.xSmall ... .xxxLarge)
|
||||
.onReceive(handle_notify(.logout)) { _ in
|
||||
try? clear_keypair()
|
||||
keypair = nil
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
/* Blank space to separate profile picture from profile editor form. */
|
||||
" " = "61b6edf1108e6f396680a33b02486a70_tr";
|
||||
|
||||
/* Description of how the nip05 identifier would be used for verification. */
|
||||
"'%@' at '%@' will be used for verification" = "'%@' bei '%@' wird zur Verifizierung benutzt werden.";
|
||||
|
||||
@@ -13,14 +10,8 @@
|
||||
/* Navigation bar title for view that shows who a user is following. */
|
||||
"(who) following" = "(who) folgt";
|
||||
|
||||
/* Prefix character to username. */
|
||||
"@" = "@";
|
||||
|
||||
/* Abbreviated version of a nostr public key. */
|
||||
"%@" = "%@";
|
||||
|
||||
/* Sentence composed of 2 variables to describe how many reposts. In source English, the first variable is the number of reposts, and the second variable is 'Repost' or 'Reposts'.
|
||||
Sentence composed of 2 variables to describe how many relay servers a user is connected. In source English, the first variable is the number of relay servers, and the second variable is 'Relay' or 'Relays'. */
|
||||
Sentence composed of 2 variables to describe how many people are following a user. In source English, the first variable is the number of followers, and the second variable is 'Follower' or 'Followers'. */
|
||||
"%@ %@" = "%@ %@";
|
||||
|
||||
/* Alert message that informs a user was blocked. */
|
||||
@@ -35,16 +26,9 @@ Sentence composed of 2 variables to describe how many relay servers a user is co
|
||||
/* Explanation of what can be done by users to earn money. There is a heading that precedes this explanation which is a variable to this string. */
|
||||
"%@. Tip your friend's posts and stack sats with Bitcoin⚡️, the native currency of the internet." = "%@. Belohne Beiträge deiner Freunde und sammle Sats mit Bitcoin⚡️, der eigenen Währung des Internets.";
|
||||
|
||||
/* Number of reposts.
|
||||
Number of relay servers a user is connected. */
|
||||
"%lld" = "%lld";
|
||||
|
||||
/* Fraction of how many of the user's relay servers that are operational. */
|
||||
"%lld/%lld" = "%lld/%lld";
|
||||
|
||||
/* Placeholder for event mention. */
|
||||
"< e >" = "< e >";
|
||||
|
||||
/* Text indicating the zap amount. i.e. number of satoshis that were tipped to a user */
|
||||
"⚡️ %@" = "⚡️ %@";
|
||||
|
||||
@@ -220,6 +204,9 @@ Number of relay servers a user is connected. */
|
||||
/* Button to pay a Lightning invoice with the user's default Lightning wallet. */
|
||||
"Default Wallet" = "Voreingestellte Wallet";
|
||||
|
||||
/* Section title for zap configuration */
|
||||
"Default Zap Amount in sats" = "Standard-Zap-Betrag in sat";
|
||||
|
||||
/* Button for deleting the users account.
|
||||
Button to delete a relay server that the user connects to.
|
||||
Button to remove a user from their blocklist.
|
||||
@@ -269,6 +256,9 @@ Number of relay servers a user is connected. */
|
||||
/* Label indicating that the below text is the EULA, an acronym for End User License Agreement. */
|
||||
"EULA" = "Endbenutzer-Lizenzvereinbarung";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Filter" = "Filter";
|
||||
|
||||
/* Button to follow a user. */
|
||||
"Follow" = "Folgen";
|
||||
|
||||
@@ -415,12 +405,18 @@ Part of a larger sentence to describe how many profiles a user is following. */
|
||||
/* Label indicating that a form input is optional. */
|
||||
"optional" = "optional";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Paid Relay" = "Kostenpflichtiger Relay";
|
||||
|
||||
/* Button to pay a Lightning invoice. */
|
||||
"Pay" = "Bezahlen";
|
||||
|
||||
/* Navigation bar title for view to pay Lightning invoice. */
|
||||
"Pay the Lightning invoice" = "Bezahle die Lightning-Rechnung";
|
||||
|
||||
/* Alert for deleting the users account. */
|
||||
"Permanently Delete Account" = "Konto dauerhaft löschen";
|
||||
|
||||
/* Dropdown option label for Lightning wallet, Phoenix. */
|
||||
"Phoenix" = "Phoenix";
|
||||
|
||||
@@ -433,10 +429,12 @@ Part of a larger sentence to describe how many profiles a user is following. */
|
||||
/* Text to indicate that what is being shown is a post from a user who has been blocked. */
|
||||
"Post from a user you've blocked" = "Nachricht von einem/e User/in den/die Du geblockt hast.";
|
||||
|
||||
/* Label for filter for seeing only posts (instead of posts and replies). */
|
||||
/* Label for filter for seeing only posts (instead of posts and replies).
|
||||
Label for filter for seeing only your posts (instead of posts and replies). */
|
||||
"Posts" = "Beiträge";
|
||||
|
||||
/* Label for filter for seeing posts and replies (instead of only posts). */
|
||||
/* Label for filter for seeing posts and replies (instead of only posts).
|
||||
Label for filter for seeing your posts and replies (instead of only your posts). */
|
||||
"Posts & Replies" = "Beiträge & Antworten";
|
||||
|
||||
/* Heading indicating that this application keeps personally identifiable information private. A sentence describing what is done to keep data private comes after this heading. */
|
||||
@@ -606,6 +604,9 @@ Part of a larger sentence to describe how many profiles a user is following. */
|
||||
/* Button for user to report that the account is impersonating someone. */
|
||||
"They are impersonating someone" = "Die geben sich für jemand anderen aus";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"This is a paid relay, you must pay for posts to be accepted." = "Das ist ein kostenpflichtiges Relay. Du musst bezahlen um Beiträge teilen zu können.";
|
||||
|
||||
/* Warning that the inputted account key is a public key and the result of what happens because of it. */
|
||||
"This is a public key, you will not be able to make posts or interact in any way. This is used for viewing accounts from their perspective." = "Dies ist ein öffentlicher Schlüssel, Du wirst keine Beiträge teilen oder oder auf irgendeine Weise interagieren können. Dies wird genutzt um andere Kontos aus deren Perspektive zu sehen.";
|
||||
|
||||
@@ -621,6 +622,9 @@ Part of a larger sentence to describe how many profiles a user is following. */
|
||||
/* Navigation bar title for note thread. */
|
||||
"Thread" = "Thema";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"To filter your %@ feed, please choose applicable relays from the list below:" = "Um deinen %@-Feed zu filtern, wähle bitte passende Relays aus der Liste unten aus:";
|
||||
|
||||
/* Button to translate note from different language. */
|
||||
"Translate Note" = "Notiz übersetzen";
|
||||
|
||||
@@ -639,9 +643,6 @@ Part of a larger sentence to describe how many profiles a user is following. */
|
||||
/* Text box prompt to ask user to type their post. */
|
||||
"Type your post here..." = "Schreibe deinen Beitrag hier...";
|
||||
|
||||
/* Non-breaking space character to fill in blank space next to event action button icons. */
|
||||
"u{00A0}" = "u{00A0}";
|
||||
|
||||
/* Button to unfollow a user. */
|
||||
"Unfollow" = "Entfolgen";
|
||||
|
||||
@@ -667,7 +668,8 @@ Part of a larger sentence to describe how many profiles a user is following. */
|
||||
Label to prompt username entry. */
|
||||
"Username" = "Benutzername";
|
||||
|
||||
/* Label to display relay software version. */
|
||||
/* Label to display relay software version.
|
||||
Section title for displaying the version number of the Damus app. */
|
||||
"Version" = "Version";
|
||||
|
||||
/* Sidebar menu label for Wallet view. */
|
||||
@@ -712,6 +714,9 @@ Part of a larger sentence to describe how many profiles a user is following. */
|
||||
/* Accessibility label for zap button */
|
||||
"Zap" = "Zap";
|
||||
|
||||
/* Navigation bar title for the Zaps view. */
|
||||
"Zaps" = "Zaps";
|
||||
|
||||
/* Dropdown option label for Lightning wallet, Zebedee. */
|
||||
"Zebedee" = "Zebedee";
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
/* Blank space to separate profile picture from profile editor form. */
|
||||
" " = "61b6edf1108e6f396680a33b02486a70_tr";
|
||||
|
||||
/* Description of how the nip05 identifier would be used for verification. */
|
||||
"'%@' at '%@' will be used for verification" = "'%@' at '%@' θα χρησιμοποιηθεί για επαλήθευση";
|
||||
|
||||
@@ -8,19 +5,13 @@
|
||||
"'%@' is an invalid NIP-05 identifier. It should look like an email." = "'%@' είναι ένα λανθασμένο NIP-05 αναγνωριστικό. Θα πρέπει να έχει την μορφή email.";
|
||||
|
||||
/* Navigation bar title for view that shows who is following a user. */
|
||||
"(Profile.displayName(profile: profile, pubkey: whos))'s Followers" = "(Profile.displayName(profile: profile, pubkey: whos))'s Ακόλουθοι";
|
||||
"(Profile.displayName(profile: profile, pubkey: whos))'s Followers" = "(Profile.displayName(profile: profile, pubkey: whos)) Ακόλουθοι";
|
||||
|
||||
/* Navigation bar title for view that shows who a user is following. */
|
||||
"(who) following" = "(ποίους) ακολουθεί";
|
||||
|
||||
/* Prefix character to username. */
|
||||
"@" = "@";
|
||||
|
||||
/* Abbreviated version of a nostr public key. */
|
||||
"%@" = "%@";
|
||||
|
||||
/* Sentence composed of 2 variables to describe how many reposts. In source English, the first variable is the number of reposts, and the second variable is 'Repost' or 'Reposts'.
|
||||
Sentence composed of 2 variables to describe how many relay servers a user is connected. In source English, the first variable is the number of relay servers, and the second variable is 'Relay' or 'Relays'. */
|
||||
Sentence composed of 2 variables to describe how many people are following a user. In source English, the first variable is the number of followers, and the second variable is 'Follower' or 'Followers'. */
|
||||
"%@ %@" = "%@ %@";
|
||||
|
||||
/* Alert message that informs a user was blocked. */
|
||||
@@ -35,16 +26,9 @@ Sentence composed of 2 variables to describe how many relay servers a user is co
|
||||
/* Explanation of what can be done by users to earn money. There is a heading that precedes this explanation which is a variable to this string. */
|
||||
"%@. Tip your friend's posts and stack sats with Bitcoin⚡️, the native currency of the internet." = "%@. Δώστε φιλοδώρημα στις δημοσιεύσεις των φίλων σας και μαζέψτε Bitcoin sats⚡️, το νόμισμα του ίντερνετ.";
|
||||
|
||||
/* Number of reposts.
|
||||
Number of relay servers a user is connected. */
|
||||
"%lld" = "%lld";
|
||||
|
||||
/* Fraction of how many of the user's relay servers that are operational. */
|
||||
"%lld/%lld" = "%lld/%lld";
|
||||
|
||||
/* Placeholder for event mention. */
|
||||
"< e >" = "< e >";
|
||||
|
||||
/* Text indicating the zap amount. i.e. number of satoshis that were tipped to a user */
|
||||
"⚡️ %@" = "⚡️ %@";
|
||||
|
||||
@@ -220,6 +204,9 @@ Number of relay servers a user is connected. */
|
||||
/* Button to pay a Lightning invoice with the user's default Lightning wallet. */
|
||||
"Default Wallet" = "Προεπιλεγμένο πορτοφόλι";
|
||||
|
||||
/* Section title for zap configuration */
|
||||
"Default Zap Amount in sats" = "Προεπιλεγμένο ποσό Zap σε sats";
|
||||
|
||||
/* Button for deleting the users account.
|
||||
Button to delete a relay server that the user connects to.
|
||||
Button to remove a user from their blocklist.
|
||||
@@ -269,6 +256,9 @@ Number of relay servers a user is connected. */
|
||||
/* Label indicating that the below text is the EULA, an acronym for End User License Agreement. */
|
||||
"EULA" = "EULA";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Filter" = "Φίλτρο";
|
||||
|
||||
/* Button to follow a user. */
|
||||
"Follow" = "Ακολουθήστε";
|
||||
|
||||
@@ -415,12 +405,18 @@ Part of a larger sentence to describe how many profiles a user is following. */
|
||||
/* Label indicating that a form input is optional. */
|
||||
"optional" = "προαιρετικό";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Paid Relay" = "Διακομιστής Relay με πληρωμή";
|
||||
|
||||
/* Button to pay a Lightning invoice. */
|
||||
"Pay" = "Πληρωμή";
|
||||
|
||||
/* Navigation bar title for view to pay Lightning invoice. */
|
||||
"Pay the Lightning invoice" = "Πληρωμή Lightning ποσού";
|
||||
|
||||
/* Alert for deleting the users account. */
|
||||
"Permanently Delete Account" = "Οριστική διαγραφή λογαριασμού";
|
||||
|
||||
/* Dropdown option label for Lightning wallet, Phoenix. */
|
||||
"Phoenix" = "Phoenix";
|
||||
|
||||
@@ -433,10 +429,12 @@ Part of a larger sentence to describe how many profiles a user is following. */
|
||||
/* Text to indicate that what is being shown is a post from a user who has been blocked. */
|
||||
"Post from a user you've blocked" = "Δημοσίευση μπλοκαρισμένου χρήστη";
|
||||
|
||||
/* Label for filter for seeing only posts (instead of posts and replies). */
|
||||
/* Label for filter for seeing only posts (instead of posts and replies).
|
||||
Label for filter for seeing only your posts (instead of posts and replies). */
|
||||
"Posts" = "Δημοσιεύσεις";
|
||||
|
||||
/* Label for filter for seeing posts and replies (instead of only posts). */
|
||||
/* Label for filter for seeing posts and replies (instead of only posts).
|
||||
Label for filter for seeing your posts and replies (instead of only your posts). */
|
||||
"Posts & Replies" = "Δημοσιεύσεις & Απαντήσεις";
|
||||
|
||||
/* Heading indicating that this application keeps personally identifiable information private. A sentence describing what is done to keep data private comes after this heading. */
|
||||
@@ -606,6 +604,9 @@ Part of a larger sentence to describe how many profiles a user is following. */
|
||||
/* Button for user to report that the account is impersonating someone. */
|
||||
"They are impersonating someone" = "Προσποιείται κάποιον άλλο";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"This is a paid relay, you must pay for posts to be accepted." = "Αυτός είναι ένας επί πληρωμή διακομιστής relay, πρέπει να πληρώσετε ώστε οι δημοσιεύσεις σας να εμφανιστούν.";
|
||||
|
||||
/* Warning that the inputted account key is a public key and the result of what happens because of it. */
|
||||
"This is a public key, you will not be able to make posts or interact in any way. This is used for viewing accounts from their perspective." = "Αυτό είναι ένα δημόσιο κλειδί, δεν θα έχετε την δυνατότητα να κάνετε δημοσιεύσεις ή να αλληλεπιδράσετε με κάποιο τρόπο. Αυτό το κλειδί είναι μόνο για προβολή / εύρεση λογαριασμών.";
|
||||
|
||||
@@ -621,6 +622,9 @@ Part of a larger sentence to describe how many profiles a user is following. */
|
||||
/* Navigation bar title for note thread. */
|
||||
"Thread" = "Νήμα";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"To filter your %@ feed, please choose applicable relays from the list below:" = "Για να φιλτράρετε την κεντρική ροή μηνυμάτων σας %@, παρακαλώ επιλέξτε σχετικούς διακομιστές relays απο την παρακάτω λίστα:";
|
||||
|
||||
/* Button to translate note from different language. */
|
||||
"Translate Note" = "Μεταφρασμένο σχόλιο";
|
||||
|
||||
@@ -639,9 +643,6 @@ Part of a larger sentence to describe how many profiles a user is following. */
|
||||
/* Text box prompt to ask user to type their post. */
|
||||
"Type your post here..." = "Πληκρολογίστε την δημοσίευσή σας εδώ...";
|
||||
|
||||
/* Non-breaking space character to fill in blank space next to event action button icons. */
|
||||
"u{00A0}" = "u{00A0}";
|
||||
|
||||
/* Button to unfollow a user. */
|
||||
"Unfollow" = "Διακοπή";
|
||||
|
||||
@@ -667,7 +668,8 @@ Part of a larger sentence to describe how many profiles a user is following. */
|
||||
Label to prompt username entry. */
|
||||
"Username" = "Όνομα χρήστη";
|
||||
|
||||
/* Label to display relay software version. */
|
||||
/* Label to display relay software version.
|
||||
Section title for displaying the version number of the Damus app. */
|
||||
"Version" = "Έκδοση";
|
||||
|
||||
/* Sidebar menu label for Wallet view. */
|
||||
@@ -712,6 +714,9 @@ Part of a larger sentence to describe how many profiles a user is following. */
|
||||
/* Accessibility label for zap button */
|
||||
"Zap" = "Zap";
|
||||
|
||||
/* Navigation bar title for the Zaps view. */
|
||||
"Zaps" = "Zaps";
|
||||
|
||||
/* Dropdown option label for Lightning wallet, Zebedee. */
|
||||
"Zebedee" = "Zebedee";
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
/* Blank space to separate profile picture from profile editor form. */
|
||||
" " = "61b6edf1108e6f396680a33b02486a70_tr";
|
||||
|
||||
/* Description of how the nip05 identifier would be used for verification. */
|
||||
"'%@' at '%@' will be used for verification" = "'%@' en '%@' se usará con fines de verificación";
|
||||
|
||||
@@ -13,12 +10,6 @@
|
||||
/* Navigation bar title for view that shows who a user is following. */
|
||||
"(who) following" = "(who) sigue a";
|
||||
|
||||
/* Prefix character to username. */
|
||||
"@" = "@";
|
||||
|
||||
/* Abbreviated version of a nostr public key. */
|
||||
"%@" = "%@";
|
||||
|
||||
/* Sentence composed of 2 variables to describe how many reposts. In source English, the first variable is the number of reposts, and the second variable is 'Repost' or 'Reposts'.
|
||||
Sentence composed of 2 variables to describe how many relay servers a user is connected. In source English, the first variable is the number of relay servers, and the second variable is 'Relay' or 'Relays'. */
|
||||
"%@ %@" = "%@ %@";
|
||||
@@ -35,16 +26,9 @@ Sentence composed of 2 variables to describe how many relay servers a user is co
|
||||
/* Explanation of what can be done by users to earn money. There is a heading that precedes this explanation which is a variable to this string. */
|
||||
"%@. Tip your friend's posts and stack sats with Bitcoin⚡️, the native currency of the internet." = "%@. Deja propinas en las publicaciones de tus amigos y acumula sats con Bitcoin⚡️, la moneda nativa de internet.";
|
||||
|
||||
/* Number of reposts.
|
||||
Number of relay servers a user is connected. */
|
||||
"%lld" = "%lld";
|
||||
|
||||
/* Fraction of how many of the user's relay servers that are operational. */
|
||||
"%lld/%lld" = "%lld/%lld";
|
||||
|
||||
/* Placeholder for event mention. */
|
||||
"< e >" = "< e >";
|
||||
|
||||
/* Text indicating the zap amount. i.e. number of satoshis that were tipped to a user */
|
||||
"⚡️ %@" = "⚡️ %@";
|
||||
|
||||
@@ -269,6 +253,9 @@ Number of relay servers a user is connected. */
|
||||
/* Label indicating that the below text is the EULA, an acronym for End User License Agreement. */
|
||||
"EULA" = "CLUF";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Filter" = "Filtro";
|
||||
|
||||
/* Button to follow a user. */
|
||||
"Follow" = "Seguir";
|
||||
|
||||
@@ -415,12 +402,18 @@ Part of a larger sentence to describe how many profiles a user is following. */
|
||||
/* Label indicating that a form input is optional. */
|
||||
"optional" = "opcional";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Paid Relay" = "Relé de pago";
|
||||
|
||||
/* Button to pay a Lightning invoice. */
|
||||
"Pay" = "Pagar";
|
||||
|
||||
/* Navigation bar title for view to pay Lightning invoice. */
|
||||
"Pay the Lightning invoice" = "Paga la factura de Lightning";
|
||||
|
||||
/* Alert for deleting the users account. */
|
||||
"Permanently Delete Account" = "Cuenta eliminada permanentemente";
|
||||
|
||||
/* Dropdown option label for Lightning wallet, Phoenix. */
|
||||
"Phoenix" = "Phoenix";
|
||||
|
||||
@@ -606,6 +599,9 @@ Part of a larger sentence to describe how many profiles a user is following. */
|
||||
/* Button for user to report that the account is impersonating someone. */
|
||||
"They are impersonating someone" = "Está suplantando a alguien";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"This is a paid relay, you must pay for posts to be accepted." = "Este es un relé de pago, debes de pagar para que acepte tus publicaciones.";
|
||||
|
||||
/* Warning that the inputted account key is a public key and the result of what happens because of it. */
|
||||
"This is a public key, you will not be able to make posts or interact in any way. This is used for viewing accounts from their perspective." = "Esta es una clave pública, por lo que no podrás hacer publicaciones ni interactuar de ningún modo. Se usa para ver cuentas desde su perspectiva.";
|
||||
|
||||
@@ -621,6 +617,9 @@ Part of a larger sentence to describe how many profiles a user is following. */
|
||||
/* Navigation bar title for note thread. */
|
||||
"Thread" = "Hilo";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"To filter your %@ feed, please choose applicable relays from the list below:" = "Para filtrar tu feed de %@, elija los relés correspondientes de la lista siguiente:";
|
||||
|
||||
/* Button to translate note from different language. */
|
||||
"Translate Note" = "Traducir nota";
|
||||
|
||||
@@ -639,9 +638,6 @@ Part of a larger sentence to describe how many profiles a user is following. */
|
||||
/* Text box prompt to ask user to type their post. */
|
||||
"Type your post here..." = "Escribe tu publicación aquí...";
|
||||
|
||||
/* Non-breaking space character to fill in blank space next to event action button icons. */
|
||||
"u{00A0}" = "u{00A0}";
|
||||
|
||||
/* Button to unfollow a user. */
|
||||
"Unfollow" = "Dejar de seguir";
|
||||
|
||||
@@ -667,7 +663,8 @@ Part of a larger sentence to describe how many profiles a user is following. */
|
||||
Label to prompt username entry. */
|
||||
"Username" = "Nombre de usuario";
|
||||
|
||||
/* Label to display relay software version. */
|
||||
/* Label to display relay software version.
|
||||
Section title for displaying the version number of the Damus app. */
|
||||
"Version" = "Versión";
|
||||
|
||||
/* Sidebar menu label for Wallet view. */
|
||||
@@ -712,6 +709,9 @@ Part of a larger sentence to describe how many profiles a user is following. */
|
||||
/* Accessibility label for zap button */
|
||||
"Zap" = "Zap";
|
||||
|
||||
/* Navigation bar title for the Zaps view. */
|
||||
"Zaps" = "Zaps";
|
||||
|
||||
/* Dropdown option label for Lightning wallet, Zebedee. */
|
||||
"Zebedee" = "Zebedee";
|
||||
|
||||
|
||||