From 5b085b4efff8603f8c6860cfa394b977740b4499 Mon Sep 17 00:00:00 2001 From: Bryan Montz Date: Wed, 30 Oct 2024 06:56:05 -0500 Subject: [PATCH 01/30] minor strings simplifications --- CHANGELOG.md | 5 +++++ Nos/Views/Home/HomeFeedView.swift | 2 +- Nos/Views/NoteComposer/ComposerActionBar.swift | 4 ++-- Nos/Views/NoteComposer/NoteComposer.swift | 2 +- Nos/Views/Onboarding/OnboardingLoginView.swift | 2 +- Nos/Views/Settings/DeleteConfirmationView.swift | 8 ++++---- Nos/Views/Settings/SettingsView.swift | 2 +- 7 files changed, 15 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc75ba5b6..9f6b631a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Release Notes + +### Internal Changes +- minor strings simplifications + ## [1.0.1] - 2024-10-28Z ### Release Notes diff --git a/Nos/Views/Home/HomeFeedView.swift b/Nos/Views/Home/HomeFeedView.swift index 80d374e1d..dd866310f 100644 --- a/Nos/Views/Home/HomeFeedView.swift +++ b/Nos/Views/Home/HomeFeedView.swift @@ -145,7 +145,7 @@ struct HomeFeedView: View { } label: { Image(systemName: "line.3.horizontal.decrease.circle") .foregroundStyle(Color.secondaryTxt) - .accessibilityLabel(Text("filter")) + .accessibilityLabel("filter") } .frame(minWidth: 40, minHeight: 40) } diff --git a/Nos/Views/NoteComposer/ComposerActionBar.swift b/Nos/Views/NoteComposer/ComposerActionBar.swift index c2cc9820e..dd9e5da5a 100644 --- a/Nos/Views/NoteComposer/ComposerActionBar.swift +++ b/Nos/Views/NoteComposer/ComposerActionBar.swift @@ -119,7 +119,7 @@ struct ComposerActionBar: View { .frame(minWidth: 44, minHeight: 44) } .padding(.leading, 8) - .accessibilityLabel(Text("attachMedia")) + .accessibilityLabel("attachMedia") } /// Expiration Time @@ -135,7 +135,7 @@ struct ComposerActionBar: View { self.expirationTime = $0 ? option.timeInterval : nil }) ) - .accessibilityLabel(Text("expirationDate")) + .accessibilityLabel("expirationDate") .padding(12) } else { Button { diff --git a/Nos/Views/NoteComposer/NoteComposer.swift b/Nos/Views/NoteComposer/NoteComposer.swift index 1aa2960c5..b94c87ff0 100644 --- a/Nos/Views/NoteComposer/NoteComposer.swift +++ b/Nos/Views/NoteComposer/NoteComposer.swift @@ -191,7 +191,7 @@ struct NoteComposer: View { } } .background(Color.appBg) - .navigationBarTitle(String(localized: "newNote"), displayMode: .inline) + .navigationBarTitle("newNote", displayMode: .inline) .toolbarBackground(.visible, for: .navigationBar) .toolbarBackground(Color.cardBgBottom, for: .navigationBar) .toolbar { diff --git a/Nos/Views/Onboarding/OnboardingLoginView.swift b/Nos/Views/Onboarding/OnboardingLoginView.swift index f92b39741..01a24a1d8 100644 --- a/Nos/Views/Onboarding/OnboardingLoginView.swift +++ b/Nos/Views/Onboarding/OnboardingLoginView.swift @@ -34,7 +34,7 @@ struct OnboardingLoginView: View { VStack { Form { Section { - SecureField(String(localized: "privateKeyPlaceholder"), text: $privateKeyString) + SecureField("privateKeyPlaceholder", text: $privateKeyString) .foregroundColor(.primaryTxt) } header: { Text("pasteYourSecretKey") diff --git a/Nos/Views/Settings/DeleteConfirmationView.swift b/Nos/Views/Settings/DeleteConfirmationView.swift index 0f52953cd..bf49be89d 100644 --- a/Nos/Views/Settings/DeleteConfirmationView.swift +++ b/Nos/Views/Settings/DeleteConfirmationView.swift @@ -50,13 +50,13 @@ struct DeleteConfirmationView: View { /// Creates the title and message view for the delete confirmation. private func titleMessageView() -> some View { VStack(spacing: 0) { - Text(String(localized: "deleteAccount")) + Text("deleteAccount") .font(.headline) .fontWeight(.bold) .padding(.bottom, 10) .padding(.top, 18) - Text(String(localized: "deleteAccountMessage")) + Text("deleteAccountMessage") .font(.footnote) .fixedSize(horizontal: false, vertical: true) .multilineTextAlignment(.center) @@ -101,7 +101,7 @@ struct DeleteConfirmationView: View { onCancel: @escaping () -> Void ) -> some View { HStack { - Button(String(localized: "cancel")) { + Button("cancel") { onCancel() } .frame(maxWidth: .infinity) @@ -111,7 +111,7 @@ struct DeleteConfirmationView: View { .frame(width: 1, height: 50) .background(Color.actionSheetDivider) - Button(String(localized: "delete")) { + Button("delete") { onDelete() } .frame(maxWidth: .infinity) diff --git a/Nos/Views/Settings/SettingsView.swift b/Nos/Views/Settings/SettingsView.swift index 5e33982ed..765d593ab 100644 --- a/Nos/Views/Settings/SettingsView.swift +++ b/Nos/Views/Settings/SettingsView.swift @@ -44,7 +44,7 @@ struct SettingsView: View { .foregroundColor(.primaryTxt) .font(.clarity(.regular, textStyle: .body)) .lineLimit(1) - .accessibilityLabel(Text("privateKey")) + .accessibilityLabel("privateKey") Spacer() From dfb22fd2a012c80fc616eb771d6417d08810d3f7 Mon Sep 17 00:00:00 2001 From: Itunu Raimi Date: Fri, 15 Nov 2024 17:16:15 +0100 Subject: [PATCH 02/30] fix overflowing UI --- Nos/Views/Components/Media/GalleryView.swift | 49 +++++++++++++++++--- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/Nos/Views/Components/Media/GalleryView.swift b/Nos/Views/Components/Media/GalleryView.swift index 83e50e4b0..ed9d15d18 100644 --- a/Nos/Views/Components/Media/GalleryView.swift +++ b/Nos/Views/Components/Media/GalleryView.swift @@ -130,17 +130,23 @@ fileprivate struct GalleryIndexView: View { private let secondaryFill = Color.galleryIndexDotSecondary /// The scale of the circles representing tabs that aren't selected, relative to `circleSize`. - private let smallScale: CGFloat = 0.75 + private let smallScale: CGFloat = 0.5 + + /// The maximum distance (in pages) from the selected index for visible circles. + /// Circles outside this range are not displayed. + private let maxDistance = 6 var body: some View { HStack(spacing: circleSpacing) { ForEach(0.. Bool { + ((currentIndex - maxDistance)...(currentIndex + maxDistance)).contains(index) + } + + /// Calculates the scale factor for a circle at a given index. + /// + /// - Parameter index: The index of the page to evaluate. + /// - Returns: A scale factor based on the distance from `currentIndex`. + private func scaleFor(_ index: Int) -> CGFloat { + // Show all circles at full size if there are 6 or fewer pages + if numberOfPages <= 6 { + return 1.0 + } + + // Calculate the distance from the selected page + let distanceFromCenter = abs(index - currentIndex) + + // Scale circles based on distance, shrinking to `smallScale` at max distance + let scaleRange = 1.0 - smallScale + let scaleFactor = 1.0 - (CGFloat(distanceFromCenter) / CGFloat(maxDistance)) * scaleRange + + // Prevent scale from dropping below `smallScale` + return max(smallScale, scaleFactor) + } } #Preview("Multiple URLs, landscape image first") { From a9bbc0804cccaecdc05723204dbf1ba0b06826d2 Mon Sep 17 00:00:00 2001 From: Itunu Raimi Date: Fri, 15 Nov 2024 17:16:24 +0100 Subject: [PATCH 03/30] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a92ca06f9..4ffa00ffd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Release Notes - Fix typo in minimum age warning - Fix crash when tapping Post button on macOS. [#1687](https://github.com/planetary-social/nos/issues/1687) +- Fix galleries expand past the width of the screen when the links are many. [#24](https://github.com/verse-pbc/issues/issues/24) ### Internal Changes From 0a258f20f86bbb83b833c2c7d0d98745ca319306 Mon Sep 17 00:00:00 2001 From: Itunu Raimi Date: Mon, 18 Nov 2024 15:08:23 +0100 Subject: [PATCH 04/30] remove unused localized strings --- Nos/Assets/Localization/Reply.xcstrings | 142 ------------------------ 1 file changed, 142 deletions(-) diff --git a/Nos/Assets/Localization/Reply.xcstrings b/Nos/Assets/Localization/Reply.xcstrings index ce815cedd..1d7e6d4b8 100644 --- a/Nos/Assets/Localization/Reply.xcstrings +++ b/Nos/Assets/Localization/Reply.xcstrings @@ -213,148 +213,6 @@ } } }, - "posted" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "hat gepostet" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "posted" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "publicado" - } - }, - "fa" : { - "stringUnit" : { - "state" : "translated", - "value" : "ارسال شده" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "a publié" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "投稿しました" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "geplaatst" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "postou" - } - }, - "sv" : { - "stringUnit" : { - "state" : "translated", - "value" : "postade" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "已发布" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "已發佈" - } - } - } - }, - "replied" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "hat geantwortet" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "replied" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "contestado" - } - }, - "fa" : { - "stringUnit" : { - "state" : "translated", - "value" : "پاسخ داد" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "a répondu" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "返信しました" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "beantwoord" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "respondeu" - } - }, - "sv" : { - "stringUnit" : { - "state" : "translated", - "value" : "svarade" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "已回复" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "已回覆" - } - } - } - }, "repliedToYourNote" : { "extractionState" : "manual", "localizations" : { From a851290aaf97cbae4a42c0039ed648e954d0a28f Mon Sep 17 00:00:00 2001 From: Itunu Raimi Date: Mon, 18 Nov 2024 15:08:54 +0100 Subject: [PATCH 05/30] remove "posted" and "replied" from UI --- Nos/Views/Components/Author/AuthorLabel.swift | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Nos/Views/Components/Author/AuthorLabel.swift b/Nos/Views/Components/Author/AuthorLabel.swift index 001f23158..0ac212483 100644 --- a/Nos/Views/Components/Author/AuthorLabel.swift +++ b/Nos/Views/Components/Author/AuthorLabel.swift @@ -9,13 +9,6 @@ struct AuthorLabel: View { var authorName = AttributedString(author.safeName) authorName.foregroundColor = .primaryTxt authorName.font = .clarity(.semibold) - if let note { - let postedOrRepliedString = String(localized: note.isReply ? "replied" : "posted", table: "Reply") - var postedOrReplied = AttributedString(" " + postedOrRepliedString) - postedOrReplied.foregroundColor = .secondaryTxt - - authorName.append(postedOrReplied) - } return authorName } From 399de64a427f6befa6708e0ee7417703e5781033 Mon Sep 17 00:00:00 2001 From: Itunu Raimi Date: Mon, 18 Nov 2024 15:10:14 +0100 Subject: [PATCH 06/30] adjust spacing and UI --- Nos/Views/Note/NoteCard.swift | 3 ++- Nos/Views/Note/NoteCardHeader.swift | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Nos/Views/Note/NoteCard.swift b/Nos/Views/Note/NoteCard.swift index d24ffdb17..d040f18f5 100644 --- a/Nos/Views/Note/NoteCard.swift +++ b/Nos/Views/Note/NoteCard.swift @@ -88,6 +88,7 @@ struct NoteCard: View { Spacer() } } + .padding(5) .allowsHitTesting(!note.isPreview) Divider().overlay(Color.cardDividerTop).shadow(color: .cardDividerTopShadow, radius: 0, x: 0, y: 1) Group { @@ -143,7 +144,7 @@ struct NoteCard: View { ReplyButton(note: note, replyAction: replyAction) } } - .padding(.leading, 13) + .padding(5) .allowsHitTesting(!note.isPreview) } } diff --git a/Nos/Views/Note/NoteCardHeader.swift b/Nos/Views/Note/NoteCardHeader.swift index 4112b24eb..beb935043 100644 --- a/Nos/Views/Note/NoteCardHeader.swift +++ b/Nos/Views/Note/NoteCardHeader.swift @@ -6,9 +6,8 @@ struct NoteCardHeader: View { @ObservedObject var author: Author var body: some View { - HStack(alignment: .center) { + HStack(alignment: .center, spacing: 8) { AuthorLabel(author: author, note: note) - Spacer() if let expirationTime = note.expirationDate?.distanceString() { Image.disappearingMessages .resizable() @@ -24,6 +23,8 @@ struct NoteCardHeader: View { .font(.clarity(.medium)) .foregroundColor(.secondaryTxt) } + + Spacer() } .padding(.leading, 10) } From e4ae00b8306a4ea751e237af9ae42d3acd5d9c25 Mon Sep 17 00:00:00 2001 From: Itunu Raimi Date: Mon, 18 Nov 2024 15:10:32 +0100 Subject: [PATCH 07/30] adjust date format to make month short form --- Nos/Extensions/Date+Elapsed.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Nos/Extensions/Date+Elapsed.swift b/Nos/Extensions/Date+Elapsed.swift index e91a503b3..e7e0f247e 100644 --- a/Nos/Extensions/Date+Elapsed.swift +++ b/Nos/Extensions/Date+Elapsed.swift @@ -44,7 +44,7 @@ extension Date { let dateFormatter = DateFormatter() dateFormatter.timeStyle = .none dateFormatter.dateFormat = DateFormatter.dateFormat( - fromTemplate: "MMMMd", + fromTemplate: "MMM d", options: 0, locale: calendar.locale ) From 2b2fb6bc91bbef1d5a58b57f9eca215b02ad505d Mon Sep 17 00:00:00 2001 From: Itunu Raimi Date: Mon, 18 Nov 2024 15:10:41 +0100 Subject: [PATCH 08/30] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a92ca06f9..ceda52738 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Release Notes - Fix typo in minimum age warning - Fix crash when tapping Post button on macOS. [#1687](https://github.com/planetary-social/nos/issues/1687) +- Adjusted note header UI to make it more readable. [#23](https://github.com/verse-pbc/issues/issues/23) ### Internal Changes From 81bec5247aae776dcb158757a192c1ef6241bd66 Mon Sep 17 00:00:00 2001 From: Itunu Raimi Date: Mon, 18 Nov 2024 15:21:18 +0100 Subject: [PATCH 09/30] fix footer leading padding --- Nos/Views/Note/NoteCard.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Nos/Views/Note/NoteCard.swift b/Nos/Views/Note/NoteCard.swift index d040f18f5..30f844bd3 100644 --- a/Nos/Views/Note/NoteCard.swift +++ b/Nos/Views/Note/NoteCard.swift @@ -144,7 +144,9 @@ struct NoteCard: View { ReplyButton(note: note, replyAction: replyAction) } } - .padding(5) + .padding(.leading, 13) + .padding(.trailing, 5) + .padding(.vertical, 5) .allowsHitTesting(!note.isPreview) } } From 1e61c0cfae4360e40860abf86b2b47f70a695c4c Mon Sep 17 00:00:00 2001 From: Crowdin Bot Date: Mon, 18 Nov 2024 16:05:41 +0000 Subject: [PATCH 10/30] New Crowdin translations by GitHub Action --- Nos/Assets/Localization/ImagePicker.xcstrings | 42 + Nos/Assets/Localization/Localizable.xcstrings | 930 ++++++------------ Nos/Assets/Localization/Moderation.xcstrings | 150 +++ Nos/Assets/Localization/Reply.xcstrings | 54 + 4 files changed, 546 insertions(+), 630 deletions(-) diff --git a/Nos/Assets/Localization/ImagePicker.xcstrings b/Nos/Assets/Localization/ImagePicker.xcstrings index 9cbc9d1db..b8fa02f5c 100644 --- a/Nos/Assets/Localization/ImagePicker.xcstrings +++ b/Nos/Assets/Localization/ImagePicker.xcstrings @@ -222,6 +222,12 @@ "value" : "Registarse para una cuenta pro de Nostr.build para la abilidad de subir archivos mas grande" } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nostr.build 계정을 통해 더 큰 파일을 업로드하세요" + } + }, "pt-BR" : { "stringUnit" : { "state" : "translated", @@ -263,6 +269,12 @@ "value" : "هنگام بارگذاری فایلی که دادید خطایی رخ داد. لطفا دوباره تلاش کنید." } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "파일을 업로드하는 동안 오류가 발생했습니다. 다시 시도하세요." + } + }, "pt-BR" : { "stringUnit" : { "state" : "translated", @@ -304,6 +316,12 @@ "value" : "Hubo un error en el intento de subir el archivo. El mensaje de error era\"%@\"" } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "파일을 업로드하는 동안 오류가 발생했습니다. 메세지: \"%@\"" + } + }, "pt-BR" : { "stringUnit" : { "state" : "translated", @@ -333,6 +351,12 @@ "value" : "Registar una Cuenta" } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "계정 생성" + } + }, "pt-BR" : { "stringUnit" : { "state" : "translated", @@ -374,6 +398,12 @@ "value" : "می توانید مجوز دوربین را در تنظیمات اپ مجاز کنید." } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "설정을 통해 카메라 권한을 허용할 수 있습니다." + } + }, "pt-BR" : { "stringUnit" : { "state" : "translated", @@ -427,6 +457,12 @@ "value" : "برای %@ به مجوز نیاز است" } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "%@를 위해 권한이 필요합니다" + } + }, "pt-BR" : { "stringUnit" : { "state" : "translated", @@ -539,6 +575,12 @@ "value" : "انتخاب از گالری عکس" } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "사진 라이브러리에서 선택하기" + } + }, "pt-BR" : { "stringUnit" : { "state" : "translated", diff --git a/Nos/Assets/Localization/Localizable.xcstrings b/Nos/Assets/Localization/Localizable.xcstrings index 0d478adff..468fc13f1 100644 --- a/Nos/Assets/Localization/Localizable.xcstrings +++ b/Nos/Assets/Localization/Localizable.xcstrings @@ -699,6 +699,12 @@ "state" : "translated", "value" : "You've finished setting up your account!" } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "계정 설정이 완료되었습니다!" + } } } }, @@ -1067,6 +1073,12 @@ "state" : "translated", "value" : "Are you over 16 years old?" } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "만 16세 이상인가요?" + } } } }, @@ -2288,6 +2300,12 @@ "state" : "translated", "value" : "¡Crea tu red!" } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "당신의 네트워크를 구축하세요" + } } } }, @@ -3838,6 +3856,12 @@ "state" : "translated", "value" : "Copy private key" } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "개인키 복사" + } } } }, @@ -3850,6 +3874,12 @@ "state" : "translated", "value" : "Copy public key" } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "공개키 복사" + } } } }, @@ -4135,6 +4165,18 @@ "state" : "translated", "value" : "Create Account" } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "계정 생성" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Skapa konto" + } } } }, @@ -5317,6 +5359,18 @@ "state" : "translated", "value" : "Display Name" } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "표시 이름" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Visningsnamn" + } } } }, @@ -5329,6 +5383,18 @@ "state" : "translated", "value" : "Your display name" } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "표시 이름" + } + }, + "sv" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ditt visningsnamn" + } } } }, @@ -6360,6 +6426,12 @@ "state" : "translated", "value" : "Español" } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "스페인어" + } } } }, @@ -6371,6 +6443,12 @@ "state" : "translated", "value" : "Food" } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "음식" + } } } }, @@ -6500,6 +6578,12 @@ "state" : "translated", "value" : "Lifestyle" } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "생활" + } } } }, @@ -6688,6 +6772,12 @@ "state" : "translated", "value" : "NZ / Australia" } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "뉴질랜드/호주" + } } } }, @@ -6699,6 +6789,12 @@ "state" : "translated", "value" : "Photography" } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "사진" + } } } }, @@ -6710,6 +6806,12 @@ "state" : "translated", "value" : "Politics" } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "정치" + } } } }, @@ -6721,6 +6823,12 @@ "state" : "translated", "value" : "Random" } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "랜덤" + } } } }, @@ -6732,6 +6840,12 @@ "state" : "translated", "value" : "Sci-Fi" } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "공상과학" + } } } }, @@ -7069,6 +7183,12 @@ "state" : "translated", "value" : "Encuentra gente" } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "사람 찾기" + } } } }, @@ -7749,24 +7869,6 @@ "state" : "translated", "value" : "Don't Block" } - }, - "es" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "No Silenciar" - } - }, - "ko" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "뮤트하지 않기" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "不静音" - } } } }, @@ -7889,24 +7991,6 @@ "state" : "translated", "value" : "Block This User?" } - }, - "es" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "¿Silenciar a este usuario?" - } - }, - "ko" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "이 유저를 뮤트할건가요?" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "静音此用户?" - } } } }, @@ -7947,24 +8031,6 @@ "state" : "translated", "value" : "Block" } - }, - "es" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "Silenciar" - } - }, - "ko" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "뮤트" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "静音" - } } } }, @@ -8012,6 +8078,12 @@ "value" : "Reportar usuario" } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "플래그 유저" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -10244,6 +10316,12 @@ "state" : "translated", "value" : "Iniciar sesión con mi cuenta" } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "내 계정으로 로그인하기" + } } } }, @@ -10659,499 +10737,169 @@ "mute" : { "extractionState" : "manual", "localizations" : { - "de" : { + "en" : { "stringUnit" : { - "state" : "needs_review", - "value" : "Stummschalten" + "state" : "translated", + "value" : "Block" } - }, + } + } + }, + "muted" : { + "extractionState" : "manual", + "localizations" : { "en" : { "stringUnit" : { "state" : "translated", - "value" : "Block" + "value" : "Blocked" } - }, - "es" : { + } + } + }, + "mutedUsers" : { + "extractionState" : "manual", + "localizations" : { + "en" : { "stringUnit" : { - "state" : "needs_review", - "value" : "Silenciar" + "state" : "translated", + "value" : "Blocked Users" } - }, - "fa" : { + } + } + }, + "mutePrompt" : { + "extractionState" : "manual", + "localizations" : { + "en" : { "stringUnit" : { - "state" : "needs_review", - "value" : "بی‌صدا" + "state" : "translated", + "value" : "Would you like to block %@?" } - }, - "fr" : { + } + } + }, + "muteUser" : { + "extractionState" : "manual", + "localizations" : { + "en" : { "stringUnit" : { - "state" : "needs_review", - "value" : "Silence" + "state" : "translated", + "value" : "Block User" } - }, - "ja" : { + } + } + }, + "mutualFriends" : { + "extractionState" : "manual", + "localizations" : { + "de" : { "stringUnit" : { - "state" : "needs_review", - "value" : "ミュート" + "state" : "translated", + "value" : "Gegenseitige Freunde" } }, - "ko" : { + "en" : { "stringUnit" : { - "state" : "needs_review", - "value" : "뮤트" + "state" : "translated", + "value" : "Mutual Friends" } }, - "nl" : { + "es" : { "stringUnit" : { - "state" : "needs_review", - "value" : "Dempen" + "state" : "translated", + "value" : "Amigos en común" } }, - "sv" : { + "fr" : { "stringUnit" : { - "state" : "needs_review", - "value" : "Tysta" + "state" : "translated", + "value" : "Amis communs" } }, - "sw" : { + "ko" : { "stringUnit" : { - "state" : "needs_review", - "value" : "Nyamazisha" + "state" : "translated", + "value" : "서로 팔로잉 하는 친구" } }, "zh-Hans" : { "stringUnit" : { - "state" : "needs_review", - "value" : "静音" + "state" : "translated", + "value" : "共同的好友" } }, "zh-Hant" : { "stringUnit" : { - "state" : "needs_review", - "value" : "靜音" + "state" : "translated", + "value" : "共同的好友" } } } }, - "muted" : { + "myKeyIsBackedUp" : { "extractionState" : "manual", "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "تم نسخ مفتاحي الخاص" + } + }, "de" : { "stringUnit" : { - "state" : "needs_review", - "value" : "Stumm geschaltet" + "state" : "translated", + "value" : "Mein Schlüssel ist gesichert" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Blocked" + "value" : "My key is backed up" } }, "es" : { "stringUnit" : { - "state" : "needs_review", - "value" : "Silenciado" + "state" : "translated", + "value" : "Mi clave está respaldada" } }, "fa" : { "stringUnit" : { - "state" : "needs_review", - "value" : "بی صدا شده" + "state" : "translated", + "value" : "کلیدم را ذخیره کردم" } }, "fr" : { "stringUnit" : { - "state" : "needs_review", - "value" : "En sourdine" + "state" : "translated", + "value" : "Ma clé est sauvegardée" } }, "ja" : { "stringUnit" : { - "state" : "needs_review", - "value" : "ミュート" + "state" : "translated", + "value" : "秘密鍵を保存しました" } }, "ko" : { "stringUnit" : { - "state" : "needs_review", - "value" : "뮤트됨" + "state" : "translated", + "value" : "내 키가 백업되었습니다." } }, "nl" : { "stringUnit" : { - "state" : "needs_review", - "value" : "Gedempt" + "state" : "translated", + "value" : "Ik heb een back-up van mijn privésleutel gemaakt" } }, - "sv" : { + "pt-BR" : { "stringUnit" : { - "state" : "needs_review", - "value" : "Tystad" + "state" : "translated", + "value" : "Minha chave está salva" } }, - "sw" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "Imenyamazishwa" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "已静音" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "已靜音" - } - } - } - }, - "mutedUsers" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "Stummgeschaltete Benutzer" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Blocked Users" - } - }, - "es" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "Usuarios Silenciados" - } - }, - "fa" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "کاربران بی صدا شده" - } - }, - "fr" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "Utilisateurs en sourdine" - } - }, - "ja" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "ミュートしたユーザー" - } - }, - "ko" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "뮤트한 사용자" - } - }, - "sv" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "Tystade användare" - } - }, - "sw" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "Watumiaji Walionyamazishwa" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "已静音的用户" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "已靜音的用戶" - } - } - } - }, - "mutePrompt" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "Möchtest du %@ stummschalten?" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Would you like to block %@?" - } - }, - "es" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "¿Te gustaría silenciar a %@?" - } - }, - "fa" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "می خواهید %@ را بیصدا کنید؟" - } - }, - "fr" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "Voulez-vous mettre en sourdine %@?" - } - }, - "ja" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "%@ をミュートしますか?" - } - }, - "ko" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "%@를 뮤트하실건가요?" - } - }, - "nl" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "Wil je %@ dempen?" - } - }, - "sv" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "Vill du tysta %@?" - } - }, - "sw" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "Je, ungependa kunyamazisha %@?" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "你是否想要静音 %@?" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "你是否想要靜音 %@?" - } - } - } - }, - "muteUser" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "Mute" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Block User" - } - }, - "es" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "Silenciar Usuario" - } - }, - "fa" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "بی صدا کردن کاربر" - } - }, - "fr" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "Mettre en sourdine" - } - }, - "ja" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "ユーザーをミュートする" - } - }, - "ko" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "뮤트하기" - } - }, - "nl" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "Demp gebruiker" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "Mutar" - } - }, - "sv" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "Tysta användare" - } - }, - "sw" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "Nyamazisha Mtumiaji" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "静音用户" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "靜音用戶" - } - } - } - }, - "mutualFriends" : { - "extractionState" : "manual", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Gegenseitige Freunde" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mutual Friends" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Amigos en común" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Amis communs" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "서로 팔로잉 하는 친구" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "共同的好友" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "共同的好友" - } - } - } - }, - "myKeyIsBackedUp" : { - "extractionState" : "manual", - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "تم نسخ مفتاحي الخاص" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mein Schlüssel ist gesichert" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "My key is backed up" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mi clave está respaldada" - } - }, - "fa" : { - "stringUnit" : { - "state" : "translated", - "value" : "کلیدم را ذخیره کردم" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ma clé est sauvegardée" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "秘密鍵を保存しました" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "내 키가 백업되었습니다." - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ik heb een back-up van mijn privésleutel gemaakt" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Minha chave está salva" - } - }, - "sv" : { + "sv" : { "stringUnit" : { "state" : "translated", "value" : "Min nyckel är säkerhetskopierad" @@ -11635,6 +11383,12 @@ "state" : "translated", "value" : "Soy nuevo en Nostr" } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nostr에 처음입니다" + } } } }, @@ -11884,6 +11638,12 @@ "value" : "utilisateur@serveur.com" } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "user@server.com" + } + }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -13351,95 +13111,11 @@ "notOldEnoughTitle" : { "extractionState" : "manual", "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "عذراً، لكن التطبيق ليس متاح لك بعد" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Entschuldigung, aber Nos ist noch nicht für dich." - } - }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Sorry, but Nos is not for you yet." } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Lo sentimos, pero Nos aún no es para ti." - } - }, - "fa" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "متاسفیم، اما نوس هنوز مناسب شما نیست،" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Désolé, mais Nos n'est pas pour vous pour l'instant." - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "申し訳ありませんが、Nos (ノス) はあなた向けではありません" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "미안하지만, Nos는 아직 사용불가에요." - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sorry, maar Nos is nog niet iets voor jou." - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Desculpa, mas o Nos não é para você ainda." - } - }, - "sv" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tyvärr, men Nos är inte för er ännu." - } - }, - "sw" : { - "stringUnit" : { - "state" : "translated", - "value" : "Pole, lakini Nos bado sio yako." - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "ขอโทษที แต่ Nos ยังไม่เหมาะกับนายในตอนนี้" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "对不起,但你还不能使用Nos。" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "對不起,但你還不能使用 Nos。" - } } } }, @@ -13964,7 +13640,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Your **@username** is your identity in the Nos community.
\nChoose a name that reflects you or your organization. Make it memorable and distinct!" + "value" : "Your **@username** is your identity in the Nos community.\u2028\nChoose a name that reflects you or your organization. Make it memorable and distinct!" } }, "es" : { @@ -14293,6 +13969,12 @@ "state" : "translated", "value" : "Private Key" } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "개인키" + } } } }, @@ -14305,6 +13987,12 @@ "state" : "translated", "value" : "(nsec)" } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "(nsec)" + } } } }, @@ -14768,6 +14456,12 @@ "state" : "translated", "value" : "Public Key" } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "공개키" + } } } }, @@ -14780,6 +14474,12 @@ "state" : "translated", "value" : "(npub)" } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "(npub)" + } } } }, @@ -15349,6 +15049,12 @@ "value" : "votrerelais.com" } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "yourrelay.com" + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -17668,6 +17374,12 @@ "state" : "translated", "value" : "Skip for now" } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "일단 건너뛰기" + } } } }, @@ -18838,83 +18550,11 @@ "unmuteUser" : { "extractionState" : "manual", "localizations" : { - "de" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "Stummschaltung aufheben" - } - }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Un-Block" } - }, - "es" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "Desilenciar" - } - }, - "fa" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "باصدا کردن" - } - }, - "fr" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "Démuseler" - } - }, - "ja" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "ミュート解除" - } - }, - "ko" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "뮤트 해제" - } - }, - "nl" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "Dempen opheffen" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "Desfazer mutado" - } - }, - "sv" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "Av-tysta" - } - }, - "sw" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "Acha Kunyamazisha" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "取消静音" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "needs_review", - "value" : "取消靜音" - } } } }, @@ -19225,7 +18865,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "Well done, you've successfully claimed your **@username**!
\nYou can share this name with other people in the Nostr and Fediverse communities to make it easy to find you." + "value" : "Well done, you've successfully claimed your **@username**!\u2028\nYou can share this name with other people in the Nostr and Fediverse communities to make it easy to find you." } }, "es" : { @@ -19287,6 +18927,12 @@ "state" : "translated", "value" : "Username" } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "유저명" + } } } }, @@ -19299,6 +18945,12 @@ "state" : "translated", "value" : "(NIP-05)" } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "(NIP-05)" + } } } }, @@ -19311,6 +18963,12 @@ "state" : "translated", "value" : "This username is not available." } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "이 이름은 사용할 수 없습니다." + } } } }, @@ -19323,6 +18981,12 @@ "state" : "translated", "value" : "username" } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "유저명" + } } } }, @@ -20038,6 +19702,12 @@ "state" : "translated", "value" : "¡Bienvenido a tu feed!" } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "피드에 오신 것을 환영합니다!" + } } } }, diff --git a/Nos/Assets/Localization/Moderation.xcstrings b/Nos/Assets/Localization/Moderation.xcstrings index a802b7ccb..04bf81eba 100644 --- a/Nos/Assets/Localization/Moderation.xcstrings +++ b/Nos/Assets/Localization/Moderation.xcstrings @@ -28,6 +28,12 @@ "value" : "پورن دو جنس" } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "양성애 음란물" + } + }, "pt-BR" : { "stringUnit" : { "state" : "translated", @@ -81,6 +87,12 @@ "value" : "صدمه جسمی" } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "신체적 피해" + } + }, "pt-BR" : { "stringUnit" : { "state" : "translated", @@ -187,6 +199,12 @@ "value" : "سوء استفاده جنسی از کودکان" } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "아동 성 학대" + } + }, "pt-BR" : { "stringUnit" : { "state" : "translated", @@ -240,6 +258,12 @@ "value" : "بدزبانی" } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "거친 표현 또는 편협함" + } + }, "pt-BR" : { "stringUnit" : { "state" : "translated", @@ -293,6 +317,12 @@ "value" : "نقض حق تکثیر" } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "저작권 위반" + } + }, "pt-BR" : { "stringUnit" : { "state" : "translated", @@ -346,6 +376,12 @@ "value" : "جرائم مواد مخدر" } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "마약 관련 범죄" + } + }, "pt-BR" : { "stringUnit" : { "state" : "translated", @@ -452,6 +488,12 @@ "value" : "ضرر مالی" } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "재정적 피해" + } + }, "pt-BR" : { "stringUnit" : { "state" : "translated", @@ -505,6 +547,12 @@ "value" : "شیادی و کلاهبرداری" } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "사기 및 스캠" + } + }, "pt-BR" : { "stringUnit" : { "state" : "translated", @@ -558,6 +606,12 @@ "value" : "پورن مردان" } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "남자 동성애 음란물" + } + }, "pt-BR" : { "stringUnit" : { "state" : "translated", @@ -611,6 +665,12 @@ "value" : "پورن بی جنسیت" } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "젠더플루이드 / 논바이너리 음란물" + } + }, "pt-BR" : { "stringUnit" : { "state" : "translated", @@ -652,6 +712,12 @@ "value" : "Acoso" } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "괴롭힘" + } + }, "pt-BR" : { "stringUnit" : { "state" : "translated", @@ -693,6 +759,12 @@ "value" : "مزاحمت یا افشای اطلاعات خصوصی" } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "괴롭힘, 스토킹 혹은 신상털기" + } + }, "pt-BR" : { "stringUnit" : { "state" : "translated", @@ -746,6 +818,12 @@ "value" : "پورن جنس مخالف" } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "이성애(헤테로) 음란물" + } + }, "pt-BR" : { "stringUnit" : { "state" : "translated", @@ -793,6 +871,12 @@ "value" : "غیر قانونی" } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "불법" + } + }, "pt-BR" : { "stringUnit" : { "state" : "translated", @@ -846,6 +930,12 @@ "value" : "جعل هویت" } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "도용 또는 사칭" + } + }, "pt-BR" : { "stringUnit" : { "state" : "translated", @@ -899,6 +989,12 @@ "value" : "عدم مدارا و نفرت" } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "해로움 및 증오" + } + }, "pt-BR" : { "stringUnit" : { "state" : "translated", @@ -952,6 +1048,12 @@ "value" : "پورن زنان" } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "여자 동성애 음란물" + } + }, "pt-BR" : { "stringUnit" : { "state" : "translated", @@ -999,6 +1101,12 @@ "value" : "احتمال آسیب" } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "해를 끼칠 가능성" + } + }, "pt-BR" : { "stringUnit" : { "state" : "translated", @@ -1146,6 +1254,12 @@ "value" : "عریانی و جنسی" } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "누드 또는 성적 콘텐츠" + } + }, "pt-BR" : { "stringUnit" : { "state" : "translated", @@ -1258,6 +1372,12 @@ "value" : "پورن" } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "음란물" + } + }, "pt-BR" : { "stringUnit" : { "state" : "translated", @@ -1305,6 +1425,12 @@ "value" : "تن فروشی" } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "성매매" + } + }, "pt-BR" : { "stringUnit" : { "state" : "translated", @@ -1346,6 +1472,12 @@ "value" : "سکس" } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "성적인" + } + }, "pt-BR" : { "stringUnit" : { "state" : "translated", @@ -1446,6 +1578,12 @@ "value" : "پورن بین جنسی" } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "트랜섹슈얼 음란물" + } + }, "pt-BR" : { "stringUnit" : { "state" : "translated", @@ -1558,6 +1696,12 @@ "value" : "خشونت بر علیه حیوان بااحساس" } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "인간에 대한 폭력" + } + }, "pt-BR" : { "stringUnit" : { "state" : "translated", @@ -1611,6 +1755,12 @@ "value" : "خشونت بر علیه انسان" } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "지각 있는 동물에 대한 폭력" + } + }, "pt-BR" : { "stringUnit" : { "state" : "translated", diff --git a/Nos/Assets/Localization/Reply.xcstrings b/Nos/Assets/Localization/Reply.xcstrings index ce815cedd..5df7a3100 100644 --- a/Nos/Assets/Localization/Reply.xcstrings +++ b/Nos/Assets/Localization/Reply.xcstrings @@ -51,6 +51,12 @@ "value" : "Unirse a la discusión" } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "토론 참여하기" + } + }, "pt-BR" : { "stringUnit" : { "state" : "translated", @@ -181,6 +187,12 @@ "value" : "返信を投稿する" } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "답글 남기기" + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -252,6 +264,12 @@ "value" : "投稿しました" } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "게시됨" + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -323,6 +341,12 @@ "value" : "返信しました" } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "답글 남김" + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -394,6 +418,12 @@ "value" : "あなたのノートに返信されました:" } }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "답글을 남김:" + } + }, "nl" : { "stringUnit" : { "state" : "translated", @@ -531,6 +561,18 @@ } } }, + "ko" : { + "variations" : { + "plural" : { + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "%lld 답글" + } + } + } + } + }, "nl" : { "variations" : { "plural" : { @@ -679,6 +721,18 @@ } } }, + "ko" : { + "variations" : { + "plural" : { + "other" : { + "stringUnit" : { + "state" : "translated", + "value" : "⚡️ 당신에게 %ld sat 잽을 보냄!" + } + } + } + } + }, "pt-BR" : { "variations" : { "plural" : { From 6ea950661a659d4d2c649086d3d2c8025b6b37a4 Mon Sep 17 00:00:00 2001 From: Itunu Raimi Date: Tue, 19 Nov 2024 14:26:01 +0100 Subject: [PATCH 11/30] fix tests --- Nos/Extensions/Date+Elapsed.swift | 2 +- NosTests/Extensions/Date+ElapsedTests.swift | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Nos/Extensions/Date+Elapsed.swift b/Nos/Extensions/Date+Elapsed.swift index e7e0f247e..cbd4214c3 100644 --- a/Nos/Extensions/Date+Elapsed.swift +++ b/Nos/Extensions/Date+Elapsed.swift @@ -89,7 +89,7 @@ extension Date { private func formatLongDate(_ calendar: Calendar) -> String { let dateFormatter = DateFormatter() dateFormatter.timeStyle = .none - dateFormatter.dateStyle = .long + dateFormatter.dateStyle = .medium dateFormatter.calendar = calendar dateFormatter.locale = calendar.locale dateFormatter.timeZone = calendar.timeZone diff --git a/NosTests/Extensions/Date+ElapsedTests.swift b/NosTests/Extensions/Date+ElapsedTests.swift index d195c72e6..f01ea4e04 100644 --- a/NosTests/Extensions/Date+ElapsedTests.swift +++ b/NosTests/Extensions/Date+ElapsedTests.swift @@ -51,28 +51,28 @@ final class Date_ElapsedTests: XCTestCase { XCTAssertEqual( try XCTUnwrap(calendar.date(byAdding: .weekOfMonth, value: -1, to: date)) .distanceString(date, calendar: calendar), - "December 2" + "Dec 2" ) XCTAssertEqual( try XCTUnwrap(calendar.date(byAdding: .month, value: -1, to: date)) .distanceString(date, calendar: calendar), - "November 9" + "Nov 9" ) XCTAssertEqual( try XCTUnwrap(calendar.date(byAdding: .year, value: -1, to: date)) .addingTimeInterval(1) .distanceString(date, calendar: calendar), - "December 9" + "Dec 9" ) XCTAssertEqual( try XCTUnwrap(calendar.date(byAdding: .year, value: -1, to: date)) .distanceString(date, calendar: calendar), - "December 9, 2022" + "Dec 9, 2022" ) XCTAssertEqual( try XCTUnwrap(calendar.date(byAdding: .year, value: -2, to: date)) .distanceString(date, calendar: calendar), - "December 9, 2021" + "Dec 9, 2021" ) } @@ -124,28 +124,28 @@ final class Date_ElapsedTests: XCTestCase { XCTAssertEqual( try XCTUnwrap(calendar.date(byAdding: .weekOfMonth, value: -1, to: date)) .distanceString(date, calendar: calendar), - "2 décembre" + "2 déc." ) XCTAssertEqual( try XCTUnwrap(calendar.date(byAdding: .month, value: -1, to: date)) .distanceString(date, calendar: calendar), - "9 novembre" + "9 nov." ) XCTAssertEqual( try XCTUnwrap(calendar.date(byAdding: .year, value: -1, to: date)) .addingTimeInterval(1) .distanceString(date, calendar: calendar), - "9 décembre" + "9 déc." ) XCTAssertEqual( try XCTUnwrap(calendar.date(byAdding: .year, value: -1, to: date)) .distanceString(date, calendar: calendar), - "9 décembre 2022" + "9 déc. 2022" ) XCTAssertEqual( try XCTUnwrap(calendar.date(byAdding: .year, value: -2, to: date)) .distanceString(date, calendar: calendar), - "9 décembre 2021" + "9 déc. 2021" ) } // swiftlint:enable function_body_length From e182974ca362a562ff49e821bcf3cfe116e63df4 Mon Sep 17 00:00:00 2001 From: rabble Date: Thu, 21 Nov 2024 07:31:08 +1300 Subject: [PATCH 12/30] adding pronouns support, still need db migration --- Nos/Assets/Localization/Localizable.xcstrings | 11 +++++++++++ Nos/Models/CoreData/Event+Hydration.swift | 1 + .../Generated/Author+CoreDataProperties.swift | 1 + .../Nos.xcdatamodeld/Nos 19.xcdatamodel/contents | 3 ++- Nos/Models/JSONEvent.swift | 4 +++- Nos/Service/CurrentUser+PublishEvents.swift | 4 +++- Nos/Views/Profile/Edit/ProfileEditView.swift | 5 +++++ 7 files changed, 26 insertions(+), 3 deletions(-) diff --git a/Nos/Assets/Localization/Localizable.xcstrings b/Nos/Assets/Localization/Localizable.xcstrings index 0d478adff..7c96f67b0 100644 --- a/Nos/Assets/Localization/Localizable.xcstrings +++ b/Nos/Assets/Localization/Localizable.xcstrings @@ -14664,6 +14664,17 @@ } } }, + "pronouns" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pronouns" + } + } + } + }, "pubkey" : { "extractionState" : "manual", "localizations" : { diff --git a/Nos/Models/CoreData/Event+Hydration.swift b/Nos/Models/CoreData/Event+Hydration.swift index a585e2b43..860c2e0a0 100644 --- a/Nos/Models/CoreData/Event+Hydration.swift +++ b/Nos/Models/CoreData/Event+Hydration.swift @@ -173,6 +173,7 @@ extension Event { newAuthor.profilePhotoURL = metadata.profilePhotoURL newAuthor.website = metadata.website newAuthor.nip05 = metadata.nip05 + newAuthor.pronouns = metadata.pronouns } catch { print("Failed to decode metaData event with ID \(String(describing: identifier))") } diff --git a/Nos/Models/CoreData/Generated/Author+CoreDataProperties.swift b/Nos/Models/CoreData/Generated/Author+CoreDataProperties.swift index 27d1338fc..174b7b7d0 100644 --- a/Nos/Models/CoreData/Generated/Author+CoreDataProperties.swift +++ b/Nos/Models/CoreData/Generated/Author+CoreDataProperties.swift @@ -17,6 +17,7 @@ extension Author { @NSManaged public var name: String? @NSManaged public var website: String? @NSManaged public var nip05: String? + @NSManaged public var pronouns: String? @NSManaged public var profilePhotoURL: URL? @NSManaged public var rawMetadata: Data? @NSManaged public var events: Set diff --git a/Nos/Models/CoreData/Nos.xcdatamodeld/Nos 19.xcdatamodel/contents b/Nos/Models/CoreData/Nos.xcdatamodeld/Nos 19.xcdatamodel/contents index 4d4c694da..81a5b1aa6 100644 --- a/Nos/Models/CoreData/Nos.xcdatamodeld/Nos 19.xcdatamodel/contents +++ b/Nos/Models/CoreData/Nos.xcdatamodeld/Nos 19.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -12,6 +12,7 @@ + diff --git a/Nos/Models/JSONEvent.swift b/Nos/Models/JSONEvent.swift index c824e5b8c..ec5b328f6 100644 --- a/Nos/Models/JSONEvent.swift +++ b/Nos/Models/JSONEvent.swift @@ -175,13 +175,14 @@ struct MetadataEventJSON: Codable { var about: String? var website: String? var picture: String? + var pronouns: String? var profilePhotoURL: URL? { URL(string: picture ?? "") } private enum CodingKeys: String, CodingKey { - case displayName = "display_name", name, nip05, about, website, picture + case displayName = "display_name", name, nip05, about, website, picture, pronouns } var dictionary: [String: String] { @@ -192,6 +193,7 @@ struct MetadataEventJSON: Codable { "about": about ?? "", "website": website ?? "", "picture": picture ?? "", + "prnouns": pronouns ?? "", ] } } diff --git a/Nos/Service/CurrentUser+PublishEvents.swift b/Nos/Service/CurrentUser+PublishEvents.swift index c12bf26b3..e24268695 100644 --- a/Nos/Service/CurrentUser+PublishEvents.swift +++ b/Nos/Service/CurrentUser+PublishEvents.swift @@ -12,7 +12,8 @@ extension CurrentUser { nip05: author.nip05, about: author.about, website: author.website, - picture: author.profilePhotoURL?.absoluteString + picture: author.profilePhotoURL?.absoluteString, + pronouns: author.pronouns ).dictionary if let rawData = author.rawMetadata { // Tack on any unsupported fields back onto the dictionary before @@ -211,6 +212,7 @@ extension CurrentUser { author.nip05 = nil author.profilePhotoURL = nil author.rawMetadata = nil + author.pronouns = nil try viewContext.save() try await publishMetadata() diff --git a/Nos/Views/Profile/Edit/ProfileEditView.swift b/Nos/Views/Profile/Edit/ProfileEditView.swift index 3b44d9068..8aa214096 100644 --- a/Nos/Views/Profile/Edit/ProfileEditView.swift +++ b/Nos/Views/Profile/Edit/ProfileEditView.swift @@ -22,6 +22,7 @@ struct ProfileEditView: View { @State private var bioText: String = "" @State private var avatarText: String = "" @State private var website: String = "" + @State private var pronouns: String = "" @State private var showNIP05Wizard = false @State private var showConfirmationDialog = false @State private var saveError: SaveProfileError? @@ -77,6 +78,8 @@ struct ProfileEditView: View { .frame(maxHeight: 200) FormSeparator() NosTextField("website", text: $website) + FormSeparator() + NosTextField("pronouns", text: $pronouns) } } .sheet(isPresented: $showNIP05Wizard) { @@ -130,6 +133,7 @@ struct ProfileEditView: View { bioText = author.about ?? "" avatarText = author.profilePhotoURL?.absoluteString ?? "" website = author.website ?? "" + pronouns = author.pronouns ?? "" } private func save() async { @@ -137,6 +141,7 @@ struct ProfileEditView: View { author.about = bioText author.profilePhotoURL = URL(string: avatarText) author.website = website + author.pronouns = pronouns do { try viewContext.save() try await currentUser.publishMetadata() From 9ed3a8f8e19910eb580c4335d09628d9923b9bff Mon Sep 17 00:00:00 2001 From: rabble Date: Thu, 21 Nov 2024 09:06:15 +1300 Subject: [PATCH 13/30] modifying the database schema and adding tests --- CHANGELOG.md | 1 + Nos.xcodeproj/project.pbxproj | 28 ++--- .../Nos.xcdatamodeld/.xccurrentversion | 2 +- .../Nos 20.xcdatamodel/.xccurrentversion | 8 ++ .../Nos.xcdatamodel/contents | 56 ++++++++++ .../Nos 20.xcdatamodel/contents | 103 ++++++++++++++++++ NosTests/Models/CoreData/AuthorTests.swift | 38 +++++++ 7 files changed, 222 insertions(+), 14 deletions(-) create mode 100644 Nos/Models/CoreData/Nos.xcdatamodeld/Nos 20.xcdatamodel/.xccurrentversion create mode 100644 Nos/Models/CoreData/Nos.xcdatamodeld/Nos 20.xcdatamodel/Nos.xcdatamodel/contents create mode 100644 Nos/Models/CoreData/Nos.xcdatamodeld/Nos 20.xcdatamodel/contents diff --git a/CHANGELOG.md b/CHANGELOG.md index 781c17400..d4298a256 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Release Notes +- Added support for user setting and displaying pronouns. - Fix typo in minimum age warning - Fix crash when tapping Post button on macOS. [#1687](https://github.com/planetary-social/nos/issues/1687) - Fix tapping follower notification not opening follower profile. [#11](https://github.com/verse-pbc/issues/issues/11) diff --git a/Nos.xcodeproj/project.pbxproj b/Nos.xcodeproj/project.pbxproj index 1d727756b..e3b54956e 100644 --- a/Nos.xcodeproj/project.pbxproj +++ b/Nos.xcodeproj/project.pbxproj @@ -712,6 +712,7 @@ 04C9D7982CC29EDD00EAAD4D /* FeaturedAuthor+Cohort5.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FeaturedAuthor+Cohort5.swift"; sourceTree = ""; }; 04F16AA62CBDBD91003AD693 /* DeleteConfirmationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteConfirmationView.swift; sourceTree = ""; }; 2D06BB9C2AE249D70085F509 /* ThreadRootView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThreadRootView.swift; sourceTree = ""; }; + 2D3C71A52CEE6F7100625BCB /* Nos 20.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Nos 20.xcdatamodel"; sourceTree = ""; }; 2D4010A12AD87DF300F93AD4 /* KnownFollowersView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KnownFollowersView.swift; sourceTree = ""; }; 3A1C296E2B2A537C0020B753 /* Moderation.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Moderation.xcstrings; sourceTree = ""; }; 3A67449B2B294712002B8DE0 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; @@ -2164,7 +2165,7 @@ C9B737702AB24D5F00398BE7 /* XCRemoteSwiftPackageReference "SwiftGenPlugin" */, C91565BF2B2368FA0068EECA /* XCRemoteSwiftPackageReference "ViewInspector" */, 3AD3185B2B294E6200026B07 /* XCRemoteSwiftPackageReference "xcstrings-tool-plugin" */, - C9FD34F42BCEC89C008F8D95 /* XCRemoteSwiftPackageReference "secp256k1.swift" */, + C9FD34F42BCEC89C008F8D95 /* XCRemoteSwiftPackageReference "secp256k1" */, C9FD35112BCED5A6008F8D95 /* XCRemoteSwiftPackageReference "nostr-sdk-ios" */, 03C49ABE2C938A9C00502321 /* XCRemoteSwiftPackageReference "SwiftSoup" */, 039389212CA4985C00698978 /* XCRemoteSwiftPackageReference "SDWebImageWebPCoder" */, @@ -2799,11 +2800,11 @@ /* Begin PBXTargetDependency section */ 3AD3185D2B294E9000026B07 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - productRef = 3AD3185C2B294E9000026B07 /* plugin:XCStringsToolPlugin */; + productRef = 3AD3185C2B294E9000026B07 /* XCStringsToolPlugin */; }; 3AEABEF32B2BF806001BC933 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - productRef = 3AEABEF22B2BF806001BC933 /* plugin:XCStringsToolPlugin */; + productRef = 3AEABEF22B2BF806001BC933 /* XCStringsToolPlugin */; }; C90862C229E9804B00C35A71 /* PBXTargetDependency */ = { isa = PBXTargetDependency; @@ -2812,11 +2813,11 @@ }; C9A6C7442AD83F7A001F9500 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - productRef = C9A6C7432AD83F7A001F9500 /* plugin:SwiftGenPlugin */; + productRef = C9A6C7432AD83F7A001F9500 /* SwiftGenPlugin */; }; C9D573402AB24A3700E06BB4 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - productRef = C9D5733F2AB24A3700E06BB4 /* plugin:SwiftGenPlugin */; + productRef = C9D5733F2AB24A3700E06BB4 /* SwiftGenPlugin */; }; C9DEBFE6298941020078B43A /* PBXTargetDependency */ = { isa = PBXTargetDependency; @@ -3673,7 +3674,7 @@ minimumVersion = 4.0.0; }; }; - C9FD34F42BCEC89C008F8D95 /* XCRemoteSwiftPackageReference "secp256k1.swift" */ = { + C9FD34F42BCEC89C008F8D95 /* XCRemoteSwiftPackageReference "secp256k1" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/GigaBitcoin/secp256k1.swift"; requirement = { @@ -3712,12 +3713,12 @@ package = 03C49ABE2C938A9C00502321 /* XCRemoteSwiftPackageReference "SwiftSoup" */; productName = SwiftSoup; }; - 3AD3185C2B294E9000026B07 /* plugin:XCStringsToolPlugin */ = { + 3AD3185C2B294E9000026B07 /* XCStringsToolPlugin */ = { isa = XCSwiftPackageProductDependency; package = 3AD3185B2B294E6200026B07 /* XCRemoteSwiftPackageReference "xcstrings-tool-plugin" */; productName = "plugin:XCStringsToolPlugin"; }; - 3AEABEF22B2BF806001BC933 /* plugin:XCStringsToolPlugin */ = { + 3AEABEF22B2BF806001BC933 /* XCStringsToolPlugin */ = { isa = XCSwiftPackageProductDependency; package = 3AD3185B2B294E6200026B07 /* XCRemoteSwiftPackageReference "xcstrings-tool-plugin" */; productName = "plugin:XCStringsToolPlugin"; @@ -3791,7 +3792,7 @@ package = C99DBF7C2A9E81CF00F7068F /* XCRemoteSwiftPackageReference "SDWebImageSwiftUI" */; productName = SDWebImageSwiftUI; }; - C9A6C7432AD83F7A001F9500 /* plugin:SwiftGenPlugin */ = { + C9A6C7432AD83F7A001F9500 /* SwiftGenPlugin */ = { isa = XCSwiftPackageProductDependency; package = C9B737702AB24D5F00398BE7 /* XCRemoteSwiftPackageReference "SwiftGenPlugin" */; productName = "plugin:SwiftGenPlugin"; @@ -3811,7 +3812,7 @@ package = C9B71DBC2A8E9BAD0031ED9F /* XCRemoteSwiftPackageReference "sentry-cocoa" */; productName = Sentry; }; - C9D5733F2AB24A3700E06BB4 /* plugin:SwiftGenPlugin */ = { + C9D5733F2AB24A3700E06BB4 /* SwiftGenPlugin */ = { isa = XCSwiftPackageProductDependency; package = C9C8450C2AB249DB00654BC1 /* XCRemoteSwiftPackageReference "SwiftGenPlugin" */; productName = "plugin:SwiftGenPlugin"; @@ -3823,12 +3824,12 @@ }; C9FD34F52BCEC89C008F8D95 /* secp256k1 */ = { isa = XCSwiftPackageProductDependency; - package = C9FD34F42BCEC89C008F8D95 /* XCRemoteSwiftPackageReference "secp256k1.swift" */; + package = C9FD34F42BCEC89C008F8D95 /* XCRemoteSwiftPackageReference "secp256k1" */; productName = secp256k1; }; C9FD34F72BCEC8B5008F8D95 /* secp256k1 */ = { isa = XCSwiftPackageProductDependency; - package = C9FD34F42BCEC89C008F8D95 /* XCRemoteSwiftPackageReference "secp256k1.swift" */; + package = C9FD34F42BCEC89C008F8D95 /* XCRemoteSwiftPackageReference "secp256k1" */; productName = secp256k1; }; C9FD35122BCED5A6008F8D95 /* NostrSDK */ = { @@ -3852,6 +3853,7 @@ C936B4572A4C7B7C00DF1EB9 /* Nos.xcdatamodeld */ = { isa = XCVersionGroup; children = ( + 2D3C71A52CEE6F7100625BCB /* Nos 20.xcdatamodel */, C95057C62CC69FD70024EC9C /* Nos 19.xcdatamodel */, C9BB9FE32CBEFF560045DC5A /* Nos 18.xcdatamodel */, C9D2839E2CB9B177007ADCB9 /* Nos 17.xcdatamodel */, @@ -3864,7 +3866,7 @@ C9C547562A4F1D1A006B0741 /* Nos 9.xcdatamodel */, 5BFF66AF2A4B55FC00AA79DD /* Nos 10.xcdatamodel */, ); - currentVersion = C95057C62CC69FD70024EC9C /* Nos 19.xcdatamodel */; + currentVersion = 2D3C71A52CEE6F7100625BCB /* Nos 20.xcdatamodel */; path = Nos.xcdatamodeld; sourceTree = ""; versionGroupType = wrapper.xcdatamodel; diff --git a/Nos/Models/CoreData/Nos.xcdatamodeld/.xccurrentversion b/Nos/Models/CoreData/Nos.xcdatamodeld/.xccurrentversion index 9cb70141b..d7c309e78 100644 --- a/Nos/Models/CoreData/Nos.xcdatamodeld/.xccurrentversion +++ b/Nos/Models/CoreData/Nos.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - Nos 19.xcdatamodel + Nos 20.xcdatamodel diff --git a/Nos/Models/CoreData/Nos.xcdatamodeld/Nos 20.xcdatamodel/.xccurrentversion b/Nos/Models/CoreData/Nos.xcdatamodeld/Nos 20.xcdatamodel/.xccurrentversion new file mode 100644 index 000000000..6c8a1eef9 --- /dev/null +++ b/Nos/Models/CoreData/Nos.xcdatamodeld/Nos 20.xcdatamodel/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + Nos.xcdatamodel + + diff --git a/Nos/Models/CoreData/Nos.xcdatamodeld/Nos 20.xcdatamodel/Nos.xcdatamodel/contents b/Nos/Models/CoreData/Nos.xcdatamodeld/Nos 20.xcdatamodel/Nos.xcdatamodel/contents new file mode 100644 index 000000000..1a418ef2c --- /dev/null +++ b/Nos/Models/CoreData/Nos.xcdatamodeld/Nos 20.xcdatamodel/Nos.xcdatamodel/contents @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Nos/Models/CoreData/Nos.xcdatamodeld/Nos 20.xcdatamodel/contents b/Nos/Models/CoreData/Nos.xcdatamodeld/Nos 20.xcdatamodel/contents new file mode 100644 index 000000000..ea7c202e8 --- /dev/null +++ b/Nos/Models/CoreData/Nos.xcdatamodeld/Nos 20.xcdatamodel/contents @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/NosTests/Models/CoreData/AuthorTests.swift b/NosTests/Models/CoreData/AuthorTests.swift index 55c33aaec..2cef55071 100644 --- a/NosTests/Models/CoreData/AuthorTests.swift +++ b/NosTests/Models/CoreData/AuthorTests.swift @@ -268,4 +268,42 @@ final class AuthorTests: CoreDataTestCase { // Assert XCTAssertEqual(authors, [eve, carl, bob]) } + + /// Test that the `pronouns` field can be set and saved correctly in Core Data. + func testSetPronouns() throws { + let context = persistenceController.viewContext + let author = try Author.findOrCreate(by: "testAuthor", context: context) + + // Set the pronouns + let pronouns = "they/them" + author.pronouns = pronouns + + // Save the context + try context.save() + + // Fetch the saved author to verify + let fetchedAuthor = try Author.find(by: "testAuthor", context: context) + + XCTAssertNotNil(fetchedAuthor) + XCTAssertEqual(fetchedAuthor?.pronouns, pronouns, "The pronouns should match the saved value.") + } + + /// Test that the `pronouns` field can be retrieved correctly from Core Data. + func testGetPronouns() throws { + let context = persistenceController.viewContext + + // Create and set up an author with pronouns + let pronouns = "she/her" + let author = try Author.findOrCreate(by: "testAuthor2", context: context) + author.pronouns = pronouns + + // Save the context + try context.save() + + // Fetch the author again and verify pronouns + let fetchedAuthor = try Author.find(by: "testAuthor2", context: context) + + XCTAssertNotNil(fetchedAuthor, "The author should have been fetched successfully.") + XCTAssertEqual(fetchedAuthor?.pronouns, pronouns, "The fetched pronouns should match the saved value.") + } } From 7cc3381b22cff8fc9223d32fc381f2def92b1c82 Mon Sep 17 00:00:00 2001 From: Josh Brown Date: Mon, 25 Nov 2024 17:03:29 -0500 Subject: [PATCH 14/30] =?UTF-8?q?correct=20pronouns=20typo,=20which=20fixe?= =?UTF-8?q?s=20the=20issue=20where=20they=E2=80=99re=20not=20saved?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Nos/Models/JSONEvent.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Nos/Models/JSONEvent.swift b/Nos/Models/JSONEvent.swift index ec5b328f6..4e32031c9 100644 --- a/Nos/Models/JSONEvent.swift +++ b/Nos/Models/JSONEvent.swift @@ -193,7 +193,7 @@ struct MetadataEventJSON: Codable { "about": about ?? "", "website": website ?? "", "picture": picture ?? "", - "prnouns": pronouns ?? "", + "pronouns": pronouns ?? "", ] } } From 5a48c67d59789fc752dae31a5bb1252d3b6714d0 Mon Sep 17 00:00:00 2001 From: Josh Brown Date: Mon, 25 Nov 2024 18:09:56 -0500 Subject: [PATCH 15/30] always show 7 dots if there are more than 7 pages; adjust dot sizes --- Nos/Views/Components/Media/GalleryView.swift | 64 +++++++++++++++----- 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/Nos/Views/Components/Media/GalleryView.swift b/Nos/Views/Components/Media/GalleryView.swift index ed9d15d18..fee9020cc 100644 --- a/Nos/Views/Components/Media/GalleryView.swift +++ b/Nos/Views/Components/Media/GalleryView.swift @@ -132,9 +132,8 @@ fileprivate struct GalleryIndexView: View { /// The scale of the circles representing tabs that aren't selected, relative to `circleSize`. private let smallScale: CGFloat = 0.5 - /// The maximum distance (in pages) from the selected index for visible circles. - /// Circles outside this range are not displayed. - private let maxDistance = 6 + /// The maximum number of circles to display. + private let maxNumberOfCircles = 7 var body: some View { HStack(spacing: circleSpacing) { @@ -160,7 +159,34 @@ fileprivate struct GalleryIndexView: View { /// - Parameter index: The index of the page to evaluate. /// - Returns: `true` if the index is within `maxDistance` of the `currentIndex`; otherwise, `false`. private func shouldShowIndex(_ index: Int) -> Bool { - ((currentIndex - maxDistance)...(currentIndex + maxDistance)).contains(index) + if numberOfPages <= maxNumberOfCircles { + return true + } + + let expectedRange = currentIndex - maxNumberOfCircles / 2 ... currentIndex + maxNumberOfCircles / 2 + let realRange: ClosedRange + if expectedRange.lowerBound < 0 { + realRange = 0...expectedRange.upperBound - expectedRange.lowerBound + } else if expectedRange.upperBound >= numberOfPages { + realRange = expectedRange.lowerBound - (expectedRange.upperBound - numberOfPages + 1)...numberOfPages - 1 + } else { + realRange = expectedRange + } + + return realRange.contains(index) + } + + private func displayRange() -> ClosedRange { + let expectedRange = currentIndex - maxNumberOfCircles / 2 ... currentIndex + maxNumberOfCircles / 2 + let realRange: ClosedRange + if expectedRange.lowerBound < 0 { + realRange = 0...expectedRange.upperBound - expectedRange.lowerBound + } else if expectedRange.upperBound >= numberOfPages { + realRange = expectedRange.lowerBound - (expectedRange.upperBound - numberOfPages + 1)...numberOfPages - 1 + } else { + realRange = expectedRange + } + return realRange } /// Calculates the scale factor for a circle at a given index. @@ -169,19 +195,29 @@ fileprivate struct GalleryIndexView: View { /// - Returns: A scale factor based on the distance from `currentIndex`. private func scaleFor(_ index: Int) -> CGFloat { // Show all circles at full size if there are 6 or fewer pages - if numberOfPages <= 6 { + if numberOfPages <= maxNumberOfCircles { return 1.0 } - // Calculate the distance from the selected page - let distanceFromCenter = abs(index - currentIndex) - - // Scale circles based on distance, shrinking to `smallScale` at max distance - let scaleRange = 1.0 - smallScale - let scaleFactor = 1.0 - (CGFloat(distanceFromCenter) / CGFloat(maxDistance)) * scaleRange - - // Prevent scale from dropping below `smallScale` - return max(smallScale, scaleFactor) + let displayRange = displayRange() + if index == currentIndex { + return 1.0 + } + if displayRange.lowerBound > 0 { + if index == displayRange.lowerBound { + return 0.5 + } else if index == displayRange.lowerBound + 1 { + return 0.75 + } + } + if displayRange.upperBound < numberOfPages - 1 { + if index == displayRange.upperBound { + return 0.5 + } else if index == displayRange.upperBound - 1 { + return 0.75 + } + } + return 1.0 } } From d57646548eb7064255febc1dcc2a374a2dec2826 Mon Sep 17 00:00:00 2001 From: rabble Date: Tue, 26 Nov 2024 12:15:35 +1300 Subject: [PATCH 16/30] Update NosTests/Models/CoreData/AuthorTests.swift Co-authored-by: Josh Brown --- NosTests/Models/CoreData/AuthorTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NosTests/Models/CoreData/AuthorTests.swift b/NosTests/Models/CoreData/AuthorTests.swift index 2cef55071..4f6cd70d0 100644 --- a/NosTests/Models/CoreData/AuthorTests.swift +++ b/NosTests/Models/CoreData/AuthorTests.swift @@ -269,7 +269,7 @@ final class AuthorTests: CoreDataTestCase { XCTAssertEqual(authors, [eve, carl, bob]) } - /// Test that the `pronouns` field can be set and saved correctly in Core Data. + /// Test that the `pronouns` field can be set and saved correctly in Core Data. func testSetPronouns() throws { let context = persistenceController.viewContext let author = try Author.findOrCreate(by: "testAuthor", context: context) From bf1e5fc864e5358426e23d33010af89b8b6ee5c1 Mon Sep 17 00:00:00 2001 From: Josh Brown Date: Tue, 26 Nov 2024 11:18:08 -0500 Subject: [PATCH 17/30] Stamping beta deployment --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 781c17400..10cb6b93b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.0.2] - 2024-11-26Z + ### Release Notes - Fix typo in minimum age warning - Fix crash when tapping Post button on macOS. [#1687](https://github.com/planetary-social/nos/issues/1687) From b409610f34f7427fa51abf40c41d98d9fb77489a Mon Sep 17 00:00:00 2001 From: Josh Brown Date: Tue, 26 Nov 2024 11:22:17 -0500 Subject: [PATCH 18/30] Bump version to 1.0.3 --- Nos.xcodeproj/project.pbxproj | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Nos.xcodeproj/project.pbxproj b/Nos.xcodeproj/project.pbxproj index 1d727756b..3a9aae362 100644 --- a/Nos.xcodeproj/project.pbxproj +++ b/Nos.xcodeproj/project.pbxproj @@ -2925,7 +2925,7 @@ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZED_STRING_SWIFTUI_SUPPORT = NO; MACOSX_DEPLOYMENT_TARGET = 13.3; - MARKETING_VERSION = 1.0.2; + MARKETING_VERSION = 1.0.3; PRODUCT_BUNDLE_IDENTIFIER = "com.verse.Nos-staging"; PRODUCT_MODULE_NAME = Nos; PRODUCT_NAME = "$(TARGET_NAME) Staging"; @@ -3099,7 +3099,7 @@ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZED_STRING_SWIFTUI_SUPPORT = NO; MACOSX_DEPLOYMENT_TARGET = 13.3; - MARKETING_VERSION = 1.0.2; + MARKETING_VERSION = 1.0.3; PRODUCT_BUNDLE_IDENTIFIER = "com.verse.Nos-dev"; PRODUCT_MODULE_NAME = Nos; PRODUCT_NAME = "$(TARGET_NAME) Dev"; @@ -3371,7 +3371,7 @@ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZED_STRING_SWIFTUI_SUPPORT = NO; MACOSX_DEPLOYMENT_TARGET = 13.3; - MARKETING_VERSION = 1.0.2; + MARKETING_VERSION = 1.0.3; PRODUCT_BUNDLE_IDENTIFIER = com.verse.Nos; PRODUCT_MODULE_NAME = Nos; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -3428,7 +3428,7 @@ LOCALIZATION_PREFERS_STRING_CATALOGS = YES; LOCALIZED_STRING_SWIFTUI_SUPPORT = NO; MACOSX_DEPLOYMENT_TARGET = 13.3; - MARKETING_VERSION = 1.0.2; + MARKETING_VERSION = 1.0.3; PRODUCT_BUNDLE_IDENTIFIER = com.verse.Nos; PRODUCT_MODULE_NAME = Nos; PRODUCT_NAME = "$(TARGET_NAME)"; From 4fb517e3a57626d211cabc4b1056c5a4f1c2670c Mon Sep 17 00:00:00 2001 From: rabble Date: Wed, 27 Nov 2024 10:20:23 +1300 Subject: [PATCH 19/30] add support for setting and displaying pronouns and also displaying user profile websites if they're set --- Nos.xcodeproj/project.pbxproj | 2 + Nos/Views/Components/BioView.swift | 6 +- Nos/Views/Components/Form/NosFormField.swift | 2 +- Nos/Views/Components/Form/NosTextEditor.swift | 2 +- Nos/Views/Components/Form/NosTextField.swift | 2 +- Nos/Views/Profile/BioSheet.swift | 59 ++++++++++++++++++- Nos/Views/Profile/ProfileHeader.swift | 17 ++++++ 7 files changed, 83 insertions(+), 7 deletions(-) diff --git a/Nos.xcodeproj/project.pbxproj b/Nos.xcodeproj/project.pbxproj index e3b54956e..33ef84c0e 100644 --- a/Nos.xcodeproj/project.pbxproj +++ b/Nos.xcodeproj/project.pbxproj @@ -154,6 +154,7 @@ 04F16AA72CBDBD91003AD693 /* DeleteConfirmationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04F16AA62CBDBD91003AD693 /* DeleteConfirmationView.swift */; }; 2D06BB9D2AE249D70085F509 /* ThreadRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D06BB9C2AE249D70085F509 /* ThreadRootView.swift */; }; 2D4010A22AD87DF300F93AD4 /* KnownFollowersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D4010A12AD87DF300F93AD4 /* KnownFollowersView.swift */; }; + 2DB44FD82CF66BAD000A6860 /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C95D68AC299E721700429F86 /* ProfileView.swift */; }; 3A1C296F2B2A537C0020B753 /* Moderation.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 3A1C296E2B2A537C0020B753 /* Moderation.xcstrings */; }; 3A67449C2B294712002B8DE0 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 3A67449B2B294712002B8DE0 /* Localizable.xcstrings */; }; 3AAB61B52B24CD0000717A07 /* Date+ElapsedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AAB61B42B24CD0000717A07 /* Date+ElapsedTests.swift */; }; @@ -2637,6 +2638,7 @@ 3AAB61B52B24CD0000717A07 /* Date+ElapsedTests.swift in Sources */, C936B45F2A4CAF2B00DF1EB9 /* AppDelegate.swift in Sources */, C96D391B2B61AFD500D3D0A1 /* RawNostrIDTests.swift in Sources */, + 2DB44FD82CF66BAD000A6860 /* ProfileView.swift in Sources */, 035729AD2BE4167E005FEE85 /* EventTests.swift in Sources */, 035729B32BE4167E005FEE85 /* TLVElementTests.swift in Sources */, C9C2B77D29E072E400548B4A /* WebSocket+Nos.swift in Sources */, diff --git a/Nos/Views/Components/BioView.swift b/Nos/Views/Components/BioView.swift index 1caff8689..d904d5c77 100644 --- a/Nos/Views/Components/BioView.swift +++ b/Nos/Views/Components/BioView.swift @@ -113,9 +113,11 @@ struct BioView: View { } private func updateShouldShowReadMore() { - shouldShowReadMore = intrinsicSize.height != truncatedSize.height + shouldShowReadMore = (author.pronouns?.isEmpty == false) || + (author.website?.isEmpty == false) || + (intrinsicSize.height > truncatedSize.height) } - + fileprivate struct IntrinsicSizePreferenceKey: PreferenceKey { static var defaultValue: CGSize = .zero static func reduce(value: inout CGSize, nextValue: () -> CGSize) {} diff --git a/Nos/Views/Components/Form/NosFormField.swift b/Nos/Views/Components/Form/NosFormField.swift index 89f39dc3d..8fe11fbde 100644 --- a/Nos/Views/Components/Form/NosFormField.swift +++ b/Nos/Views/Components/Form/NosFormField.swift @@ -43,7 +43,7 @@ struct NosFormField_Previews: PreviewProvider { WithState(initialValue: "") { text in NosFormField("about") { TextField("", text: text) - .textInputAutocapitalization(.none) + .textInputAutocapitalization(.never) .foregroundColor(.primaryTxt) .autocorrectionDisabled() } diff --git a/Nos/Views/Components/Form/NosTextEditor.swift b/Nos/Views/Components/Form/NosTextEditor.swift index 5098c6a46..6b351305b 100644 --- a/Nos/Views/Components/Form/NosTextEditor.swift +++ b/Nos/Views/Components/Form/NosTextEditor.swift @@ -14,7 +14,7 @@ struct NosTextEditor: View { var body: some View { NosFormField(label) { TextEditor(text: $text) - .textInputAutocapitalization(.none) + .textInputAutocapitalization(.never) .foregroundColor(.primaryTxt) .scrollContentBackground(.hidden) .autocorrectionDisabled() diff --git a/Nos/Views/Components/Form/NosTextField.swift b/Nos/Views/Components/Form/NosTextField.swift index 25988ac63..f4fc2b86f 100644 --- a/Nos/Views/Components/Form/NosTextField.swift +++ b/Nos/Views/Components/Form/NosTextField.swift @@ -14,7 +14,7 @@ struct NosTextField: View { var body: some View { NosFormField(label) { TextField("", text: $text) - .textInputAutocapitalization(.none) + .textInputAutocapitalization(.never) .foregroundColor(.primaryTxt) .autocorrectionDisabled() } diff --git a/Nos/Views/Profile/BioSheet.swift b/Nos/Views/Profile/BioSheet.swift index 46be5d1f1..f71a95f60 100644 --- a/Nos/Views/Profile/BioSheet.swift +++ b/Nos/Views/Profile/BioSheet.swift @@ -19,7 +19,21 @@ struct BioSheet: View { ) return bio } - + + private var pronouns: String? { + guard let pronouns = author.pronouns, !pronouns.isEmpty else { + return nil + } + return pronouns + } + + private var website: String? { + guard let website = author.website, !website.isEmpty else { + return nil + } + return website + } + var body: some View { ScrollView(.vertical) { VStack(alignment: .leading, spacing: 13) { @@ -43,7 +57,48 @@ struct BioSheet: View { if author.hasMostrNIP05 { ActivityPubBadgeView(author: author) } - + + if let website { + Text("website") + .font(.subheadline.weight(.semibold)) + .foregroundStyle(Color.secondaryTxt) + .lineSpacing(10) + .shadow( + color: Color.bioSheetShadow, + radius: 4, + x: 0, + y: 4 + ) + .padding(.top, 34) + Link(destination: URL(string: website) ?? URL(string: "https://nos.social")!, + label: { + Text(website) + .textSelection(.enabled) + .font(.body) + .foregroundStyle(Color.primaryTxt) + .tint(.accent) + }) + } + + if let pronouns { + Text("pronouns") + .font(.subheadline.weight(.semibold)) + .foregroundStyle(Color.secondaryTxt) + .lineSpacing(10) + .shadow( + color: Color.bioSheetShadow, + radius: 4, + x: 0, + y: 4 + ) + .padding(.top, 34) + Text(pronouns) + .textSelection(.enabled) + .font(.body) + .foregroundStyle(Color.primaryTxt) + .tint(.accent) + } + if let bio { Text("bio") .font(.subheadline.weight(.semibold)) diff --git a/Nos/Views/Profile/ProfileHeader.swift b/Nos/Views/Profile/ProfileHeader.swift index c68b304d8..c1bb78ade 100644 --- a/Nos/Views/Profile/ProfileHeader.swift +++ b/Nos/Views/Profile/ProfileHeader.swift @@ -34,6 +34,21 @@ struct ProfileHeader: View { } return false } + + private var shouldShowWebsite: Bool { + if let website = author.website { + return website.isEmpty == false + } + return false + } + + private var shouldShowPronouns: Bool { + if let pronouns = author.pronouns { + return pronouns.isEmpty == false + } + return false + } + private var knownFollowers: [Follow] { author.followers.filter { @@ -139,6 +154,8 @@ struct ProfileHeader: View { divider .padding(.top, shouldShowBio ? 0 : 16) + + if let first = knownFollowers[safe: 0]?.source { Button { From ee9f64a1e04aad61216f6b45436f3c5c501f03f3 Mon Sep 17 00:00:00 2001 From: rabble Date: Wed, 27 Nov 2024 10:26:18 +1300 Subject: [PATCH 20/30] adding website link in profile to the changelog --- CHANGELOG.md | 1 + Nos/{Controller => Views/Profile}/NoteWarningController.swift | 0 2 files changed, 1 insertion(+) rename Nos/{Controller => Views/Profile}/NoteWarningController.swift (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4298a256..df7c82766 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Release Notes - Added support for user setting and displaying pronouns. +- Added display of website urls for user profiles. - Fix typo in minimum age warning - Fix crash when tapping Post button on macOS. [#1687](https://github.com/planetary-social/nos/issues/1687) - Fix tapping follower notification not opening follower profile. [#11](https://github.com/verse-pbc/issues/issues/11) diff --git a/Nos/Controller/NoteWarningController.swift b/Nos/Views/Profile/NoteWarningController.swift similarity index 100% rename from Nos/Controller/NoteWarningController.swift rename to Nos/Views/Profile/NoteWarningController.swift From e41c559e023afd9e2fa23d78c79b0fb6e2fdf9a0 Mon Sep 17 00:00:00 2001 From: rabble Date: Wed, 27 Nov 2024 10:28:00 +1300 Subject: [PATCH 21/30] updating project file --- Nos.xcodeproj/project.pbxproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Nos.xcodeproj/project.pbxproj b/Nos.xcodeproj/project.pbxproj index 33ef84c0e..c035cb998 100644 --- a/Nos.xcodeproj/project.pbxproj +++ b/Nos.xcodeproj/project.pbxproj @@ -1622,6 +1622,7 @@ isa = PBXGroup; children = ( 5B29B58D2BEC392B008F6008 /* ActivityPubBadgeView.swift */, + C913DA092AEAF52B003BDD6D /* NoteWarningController.swift */, 5B29B5832BEAA0D7008F6008 /* BioSheet.swift */, A32B6C7229A6BE9B00653FF5 /* FollowsView.swift */, 5BFF66B52A58A8A000AA79DD /* MutesView.swift */, @@ -1972,7 +1973,6 @@ 0357299A2BE415E5005FEE85 /* ContentWarningController.swift */, C913DA0B2AEB2EBF003BDD6D /* FetchRequestPublisher.swift */, C993148C2C5BD8FC00224BA6 /* NoteEditorController.swift */, - C913DA092AEAF52B003BDD6D /* NoteWarningController.swift */, C98CA9062B14FBBF00929141 /* PagedNoteDataSource.swift */, C9DEBFD3298941000078B43A /* PersistenceController.swift */, C97A1C8A29E45B4E009D9E8D /* RawEventController.swift */, From 3dbbf4d568f86fc9c1b27cd03ff18fd7292f7998 Mon Sep 17 00:00:00 2001 From: Josh Brown Date: Tue, 26 Nov 2024 17:15:51 -0500 Subject: [PATCH 22/30] fixed issue where websites without https:// would not open also: fixed lint errors and a warning --- Nos/Views/Components/BioView.swift | 2 +- Nos/Views/Onboarding/AccountSuccessView.swift | 2 +- Nos/Views/Profile/BioSheet.swift | 27 ++++++++++++++++--- Nos/Views/Profile/ProfileHeader.swift | 3 --- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/Nos/Views/Components/BioView.swift b/Nos/Views/Components/BioView.swift index d904d5c77..b7a4c7fd5 100644 --- a/Nos/Views/Components/BioView.swift +++ b/Nos/Views/Components/BioView.swift @@ -113,7 +113,7 @@ struct BioView: View { } private func updateShouldShowReadMore() { - shouldShowReadMore = (author.pronouns?.isEmpty == false) || + shouldShowReadMore = (author.pronouns?.isEmpty == false) || (author.website?.isEmpty == false) || (intrinsicSize.height > truncatedSize.height) } diff --git a/Nos/Views/Onboarding/AccountSuccessView.swift b/Nos/Views/Onboarding/AccountSuccessView.swift index c8ce69a74..a8a7666a8 100644 --- a/Nos/Views/Onboarding/AccountSuccessView.swift +++ b/Nos/Views/Onboarding/AccountSuccessView.swift @@ -134,7 +134,7 @@ fileprivate struct ConnectingLine: Shape { } #Preview("All steps completed") { - var state = OnboardingState() + let state = OnboardingState() state.displayNameSucceeded = true state.usernameSucceeded = true diff --git a/Nos/Views/Profile/BioSheet.swift b/Nos/Views/Profile/BioSheet.swift index f71a95f60..9bc0c31aa 100644 --- a/Nos/Views/Profile/BioSheet.swift +++ b/Nos/Views/Profile/BioSheet.swift @@ -33,6 +33,18 @@ struct BioSheet: View { } return website } + + private var websiteURL: URL? { + guard let website = author.website, !website.isEmpty, let url = URL(string: website) else { + return nil + } + + guard let scheme = url.scheme, !scheme.isEmpty else { + return URL(string: "https://\(website)") + } + + return url + } var body: some View { ScrollView(.vertical) { @@ -70,14 +82,23 @@ struct BioSheet: View { y: 4 ) .padding(.top, 34) - Link(destination: URL(string: website) ?? URL(string: "https://nos.social")!, - label: { + + if let websiteURL { + Link(destination: websiteURL) { + Text(website) + .textSelection(.enabled) + .font(.body) + .foregroundStyle(Color.primaryTxt) + .tint(.accent) + } + .underline() + } else { Text(website) .textSelection(.enabled) .font(.body) .foregroundStyle(Color.primaryTxt) .tint(.accent) - }) + } } if let pronouns { diff --git a/Nos/Views/Profile/ProfileHeader.swift b/Nos/Views/Profile/ProfileHeader.swift index c1bb78ade..e916c9f10 100644 --- a/Nos/Views/Profile/ProfileHeader.swift +++ b/Nos/Views/Profile/ProfileHeader.swift @@ -49,7 +49,6 @@ struct ProfileHeader: View { return false } - private var knownFollowers: [Follow] { author.followers.filter { guard let source = $0.source else { @@ -154,8 +153,6 @@ struct ProfileHeader: View { divider .padding(.top, shouldShowBio ? 0 : 16) - - if let first = knownFollowers[safe: 0]?.source { Button { From cdb96e7f171f9e1259aa1bc8c513f394f78a37fb Mon Sep 17 00:00:00 2001 From: Josh Brown Date: Tue, 26 Nov 2024 17:39:36 -0500 Subject: [PATCH 23/30] revert unneded change to Nos 19 data model --- .../CoreData/Nos.xcdatamodeld/Nos 19.xcdatamodel/contents | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Nos/Models/CoreData/Nos.xcdatamodeld/Nos 19.xcdatamodel/contents b/Nos/Models/CoreData/Nos.xcdatamodeld/Nos 19.xcdatamodel/contents index 81a5b1aa6..4d4c694da 100644 --- a/Nos/Models/CoreData/Nos.xcdatamodeld/Nos 19.xcdatamodel/contents +++ b/Nos/Models/CoreData/Nos.xcdatamodeld/Nos 19.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -12,7 +12,6 @@ - From 61d9e09742b2f99386ca63c65ada1ee31988e1ea Mon Sep 17 00:00:00 2001 From: Josh Brown Date: Tue, 26 Nov 2024 17:59:07 -0500 Subject: [PATCH 24/30] update doc comment spacing in AuthorTests --- NosTests/Models/CoreData/AuthorTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NosTests/Models/CoreData/AuthorTests.swift b/NosTests/Models/CoreData/AuthorTests.swift index 4f6cd70d0..92a604eb8 100644 --- a/NosTests/Models/CoreData/AuthorTests.swift +++ b/NosTests/Models/CoreData/AuthorTests.swift @@ -288,7 +288,7 @@ final class AuthorTests: CoreDataTestCase { XCTAssertEqual(fetchedAuthor?.pronouns, pronouns, "The pronouns should match the saved value.") } - /// Test that the `pronouns` field can be retrieved correctly from Core Data. + /// Test that the `pronouns` field can be retrieved correctly from Core Data. func testGetPronouns() throws { let context = persistenceController.viewContext From f2ee81a3dfcdca2d683b6d6d3fc0ac198b088f4c Mon Sep 17 00:00:00 2001 From: Josh Brown Date: Tue, 26 Nov 2024 18:01:22 -0500 Subject: [PATCH 25/30] remove indentation --- Nos/Views/Components/BioView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Nos/Views/Components/BioView.swift b/Nos/Views/Components/BioView.swift index b7a4c7fd5..e555b66b0 100644 --- a/Nos/Views/Components/BioView.swift +++ b/Nos/Views/Components/BioView.swift @@ -117,7 +117,7 @@ struct BioView: View { (author.website?.isEmpty == false) || (intrinsicSize.height > truncatedSize.height) } - + fileprivate struct IntrinsicSizePreferenceKey: PreferenceKey { static var defaultValue: CGSize = .zero static func reduce(value: inout CGSize, nextValue: () -> CGSize) {} From 3d390f8fe3c15c81dea2d23f2034e44d1b30383a Mon Sep 17 00:00:00 2001 From: Josh Brown Date: Tue, 26 Nov 2024 18:02:54 -0500 Subject: [PATCH 26/30] move controller back --- Nos.xcodeproj/project.pbxproj | 2 +- Nos/{Views/Profile => Controller}/NoteWarningController.swift | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename Nos/{Views/Profile => Controller}/NoteWarningController.swift (100%) diff --git a/Nos.xcodeproj/project.pbxproj b/Nos.xcodeproj/project.pbxproj index afa992ab5..df92056c0 100644 --- a/Nos.xcodeproj/project.pbxproj +++ b/Nos.xcodeproj/project.pbxproj @@ -1622,7 +1622,6 @@ isa = PBXGroup; children = ( 5B29B58D2BEC392B008F6008 /* ActivityPubBadgeView.swift */, - C913DA092AEAF52B003BDD6D /* NoteWarningController.swift */, 5B29B5832BEAA0D7008F6008 /* BioSheet.swift */, A32B6C7229A6BE9B00653FF5 /* FollowsView.swift */, 5BFF66B52A58A8A000AA79DD /* MutesView.swift */, @@ -1973,6 +1972,7 @@ 0357299A2BE415E5005FEE85 /* ContentWarningController.swift */, C913DA0B2AEB2EBF003BDD6D /* FetchRequestPublisher.swift */, C993148C2C5BD8FC00224BA6 /* NoteEditorController.swift */, + C913DA092AEAF52B003BDD6D /* NoteWarningController.swift */, C98CA9062B14FBBF00929141 /* PagedNoteDataSource.swift */, C9DEBFD3298941000078B43A /* PersistenceController.swift */, C97A1C8A29E45B4E009D9E8D /* RawEventController.swift */, diff --git a/Nos/Views/Profile/NoteWarningController.swift b/Nos/Controller/NoteWarningController.swift similarity index 100% rename from Nos/Views/Profile/NoteWarningController.swift rename to Nos/Controller/NoteWarningController.swift From 563853526c976ba095de5512e4c3b29370ffec6d Mon Sep 17 00:00:00 2001 From: Josh Brown Date: Tue, 26 Nov 2024 18:03:36 -0500 Subject: [PATCH 27/30] Update CHANGELOG.md --- CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1116241a0..b5f3cc134 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,11 +10,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [1.0.2] - 2024-11-26Z - ### Release Notes - Added support for user setting and displaying pronouns. - Added display of website urls for user profiles. + +## [1.0.2] - 2024-11-26Z + +### Release Notes - Fix typo in minimum age warning - Fix crash when tapping Post button on macOS. [#1687](https://github.com/planetary-social/nos/issues/1687) - Fix tapping follower notification not opening follower profile. [#11](https://github.com/verse-pbc/issues/issues/11) From 4fbf34909398d756472aa657572f6dcc8d1e6700 Mon Sep 17 00:00:00 2001 From: Josh Brown Date: Wed, 27 Nov 2024 11:24:20 -0500 Subject: [PATCH 28/30] remove ProfileView from test target to fix build --- Nos.xcodeproj/project.pbxproj | 2 -- 1 file changed, 2 deletions(-) diff --git a/Nos.xcodeproj/project.pbxproj b/Nos.xcodeproj/project.pbxproj index df92056c0..680d715e8 100644 --- a/Nos.xcodeproj/project.pbxproj +++ b/Nos.xcodeproj/project.pbxproj @@ -154,7 +154,6 @@ 04F16AA72CBDBD91003AD693 /* DeleteConfirmationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04F16AA62CBDBD91003AD693 /* DeleteConfirmationView.swift */; }; 2D06BB9D2AE249D70085F509 /* ThreadRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D06BB9C2AE249D70085F509 /* ThreadRootView.swift */; }; 2D4010A22AD87DF300F93AD4 /* KnownFollowersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2D4010A12AD87DF300F93AD4 /* KnownFollowersView.swift */; }; - 2DB44FD82CF66BAD000A6860 /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C95D68AC299E721700429F86 /* ProfileView.swift */; }; 3A1C296F2B2A537C0020B753 /* Moderation.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 3A1C296E2B2A537C0020B753 /* Moderation.xcstrings */; }; 3A67449C2B294712002B8DE0 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 3A67449B2B294712002B8DE0 /* Localizable.xcstrings */; }; 3AAB61B52B24CD0000717A07 /* Date+ElapsedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AAB61B42B24CD0000717A07 /* Date+ElapsedTests.swift */; }; @@ -2638,7 +2637,6 @@ 3AAB61B52B24CD0000717A07 /* Date+ElapsedTests.swift in Sources */, C936B45F2A4CAF2B00DF1EB9 /* AppDelegate.swift in Sources */, C96D391B2B61AFD500D3D0A1 /* RawNostrIDTests.swift in Sources */, - 2DB44FD82CF66BAD000A6860 /* ProfileView.swift in Sources */, 035729AD2BE4167E005FEE85 /* EventTests.swift in Sources */, 035729B32BE4167E005FEE85 /* TLVElementTests.swift in Sources */, C9C2B77D29E072E400548B4A /* WebSocket+Nos.swift in Sources */, From ed724d6e0e01d39010770de2e878a5407f794b07 Mon Sep 17 00:00:00 2001 From: Josh Brown Date: Wed, 27 Nov 2024 12:16:40 -0500 Subject: [PATCH 29/30] delete duplicate files --- Nos/Views/Media/AspectRatioContainer.swift | 40 ---- Nos/Views/Media/GalleryView.swift | 214 --------------------- Nos/Views/Media/ImageButton.swift | 92 --------- Nos/Views/Media/ImageViewer.swift | 78 -------- Nos/Views/Media/LinkPreview.swift | 141 -------------- Nos/Views/Media/LinkView.swift | 27 --- Nos/Views/Media/SquareImage.swift | 24 --- Nos/Views/Media/ZoomableContainer.swift | 123 ------------ 8 files changed, 739 deletions(-) delete mode 100644 Nos/Views/Media/AspectRatioContainer.swift delete mode 100644 Nos/Views/Media/GalleryView.swift delete mode 100644 Nos/Views/Media/ImageButton.swift delete mode 100644 Nos/Views/Media/ImageViewer.swift delete mode 100644 Nos/Views/Media/LinkPreview.swift delete mode 100644 Nos/Views/Media/LinkView.swift delete mode 100644 Nos/Views/Media/SquareImage.swift delete mode 100644 Nos/Views/Media/ZoomableContainer.swift diff --git a/Nos/Views/Media/AspectRatioContainer.swift b/Nos/Views/Media/AspectRatioContainer.swift deleted file mode 100644 index 0d5d3cca0..000000000 --- a/Nos/Views/Media/AspectRatioContainer.swift +++ /dev/null @@ -1,40 +0,0 @@ -import SwiftUI - -/// A container that holds a view and crops it to fit the aspect ratio of determined by the ``orientation``. -/// When the ``orientation`` is `.portrait`, the aspect ratio of the container will be 3:4. Otherwise, it'll be 4:3. -struct AspectRatioContainer: View { - /// The orientation, which determines the aspect ratio of this container. - let orientation: MediaOrientation - - /// The content to be displayed in the container. - let content: () -> Content - - var body: some View { - Color.clear - .aspectRatio(orientation.aspectRatio, contentMode: .fit) - .overlay { - content() - } - .clipShape(.rect) - .contentShape(.rect) - } -} - -/// The orientation of the media: either landscape or portrait. -enum MediaOrientation { - /// The media is wider than it is tall. - case landscape - /// The media is taller than it is wide. - case portrait - - /// The aspect ratio to use for the view that displays this media. - /// For `landscape`, the aspect ratio will be 4:3. For `portrait`, the aspect ratio will be 3:4. - var aspectRatio: CGFloat { - switch self { - case .landscape: - 4 / 3 - case .portrait: - 3 / 4 - } - } -} diff --git a/Nos/Views/Media/GalleryView.swift b/Nos/Views/Media/GalleryView.swift deleted file mode 100644 index f502560c3..000000000 --- a/Nos/Views/Media/GalleryView.swift +++ /dev/null @@ -1,214 +0,0 @@ -import Dependencies -import SwiftUI - -/// Displays an array of URLs in a tab view with custom paging indicators. -/// If only one URL is provided, displays a ``LinkView`` with the URL. -struct GalleryView: View { - /// The URLs of the content to display. - let urls: [URL] - - /// The currently-selected tab in the tab view. - @State private var selectedTab = 0 - - /// The orientation of all media in this gallery view. Initially set to `.landscape` until we load the first URL and - /// determine its orientation, then updated to match the first item's orientation. - @State private var orientation: MediaOrientation? - - /// This essential first image determines the orientation of the gallery view. Whatever orientation this is, so the - /// rest shall be. - /// Oh, but also: it's not always an image, so this won't work if it's a video or web link. Oopsie. - @State private var firstImage: Image? - - /// The media service that loads content from URLs and determines the orientation for this gallery. - @Dependency(\.mediaService) private var mediaService - - var body: some View { - if let orientation { - if urls.count == 1, let url = urls.first { - linkView(url: url, orientation: orientation) - } else { - galleryView(orientation: orientation) - } - } else { - loadingView() - } - } - - /// Produces a tab view with custom paging indicators in the given orientation. - /// - Parameter orientation: The orientation to use for the gallery. - /// - Returns: A gallery view in the given orientation. - private func galleryView(orientation: MediaOrientation) -> some View { - VStack { - TabView(selection: $selectedTab) { - ForEach(urls.indices, id: \.self) { index in - AspectRatioContainer(orientation: orientation) { - LinkView(url: urls[index]) - .tag(index) - } - } - } - .tabViewStyle(.page(indexDisplayMode: .never)) - .frame( - minWidth: 0, - maxWidth: .infinity, - minHeight: 0, - maxHeight: .infinity - ) - .aspectRatio(orientation == .portrait ? 3 / 4 : 4 / 3, contentMode: .fit) - .padding(.bottom, 10) - .clipShape(.rect) - - GalleryIndexView(numberOfPages: urls.count, currentIndex: selectedTab) - } - .padding(.bottom, 10) - } - - /// Produces a ``LinkView`` with the given URL in the given orientation. - /// - Parameters: - /// - url: The URL to display in the ``LinkView``. - /// - orientation: The orientation to use for the ``LinkView``. - /// - Returns: A ``LinkView`` with the given URL in the given orientation. - private func linkView(url: URL, orientation: MediaOrientation) -> some View { - AspectRatioContainer(orientation: orientation) { - LinkView(url: url) - } - } - - /// A loading view that fills the space for the given `loadingOrientation` and loads the first URL to determine the - /// orientation for the gallery. - /// - Parameter loadingOrientation: The ``MediaOrientation`` to use to display the loading view. - /// Defaults to `.landscape`. - /// - Returns: A loading view in the given `loadingOrientation`. - private func loadingView(_ loadingOrientation: MediaOrientation = .landscape) -> some View { - AspectRatioContainer(orientation: loadingOrientation) { - ProgressView() - } - .task { - guard let url = urls.first else { - orientation = .landscape - return - } - - orientation = await mediaService.orientation(for: url) - } - } -} - -// Thanks for the [example](https://betterprogramming.pub/custom-paging-ui-in-swiftui-13f1347cf529) Alexandru Turcanu! - -/// Custom paging indicators for a `GalleryView`. -fileprivate struct GalleryIndexView: View { - /// The number of pages in the tab view. - let numberOfPages: Int - - /// The currently-selected tab in the tab view. - let currentIndex: Int - - /// The size of the circle representing the currently-selected tab. - private let circleSize: CGFloat = 8.0 - - /// The space between circles. - private let circleSpacing: CGFloat = 6.0 - - /// The fill style of the circle indicating which tab is selected. - private let primaryFill = LinearGradient.horizontalAccent - - /// The fill style of the circles indicating tabs that are not selected. - private let secondaryFill = Color.galleryIndexDotSecondary - - /// The scale of the circles representing tabs that aren't selected, relative to `circleSize`. - private let smallScale: CGFloat = 0.75 - - var body: some View { - HStack(spacing: circleSpacing) { - ForEach(0.. LPLinkView { - let linkView = LPLinkView(url: url) - linkView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) - linkView.setContentCompressionResistancePriority(.defaultLow, for: .vertical) - linkView.sizeToFit() - return linkView - } - - func updateUIView(_ uiView: LPLinkView, context: Context) { - let provider = LPMetadataProvider() - provider.startFetchingMetadata(for: url) { metadata, error in - guard let metadata = metadata, error == nil else { return } - DispatchQueue.main.async { - uiView.metadata = metadata - } - } - } -} - -struct LinkPreviewCarousel: View { - - var links: [URL] - - var body: some View { - if links.count == 1, let url = links.first { - - if url.isImage { - HeroImageButton(url: url) - } else { - LinkPreview(url: url) - .padding(.horizontal, 15) - .padding(.vertical, 0) - .padding(.bottom, 15) - } - } else { - TabView { - ForEach(links, id: \.self.absoluteURL) { url in - LinkPreview(url: url) - .padding(.horizontal, 15) - .padding(.vertical, 0) - } - } - .tabViewStyle(.page) - .frame(height: 320) - .padding(.bottom, 15) - } - } -} - -struct LinkPreview_Previews: PreviewProvider { - static var previews: some View { - Group { - // swiftlint:disable line_length - LinkPreviewCarousel(links: [ - URL(string: "https://image.nostr.build/77713e6c2ef5186d516a6f16fb197fca53a20677c6756a9de1afc2d5e6d96548.jpg")!, - URL(string: "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Ftse1.mm.bing.net%2Fth%3Fid%3DOIP.r1ZOH5E3M6WiK6aw5GRdlAHaEK%26pid%3DApi&f=1&ipt=42ae9de7730da3bda152c5980cd64b14ccef37d8f55b8791e41b4667fc38ddf1&ipo=images")!, - URL(string: "https://youtu.be/5qvdbyRH9wA?si=y_KTgLR22nH0-cs8")!, - URL(string: "https://image.nostr.build/d5e38e7d864a344872d922d7f78daf67b0d304932fcb7fe22d35263c2fcf11c2.jpg")!, - ]) - - LinkPreviewCarousel(links: [ - URL(string: "https://image.nostr.build/d5e38e7d864a344872d922d7f78daf67b0d304932fcb7fe22d35263c2fcf11c2.jpg")! - ]) - // swiftlint:enable line_length - } - } -} diff --git a/Nos/Views/Media/LinkView.swift b/Nos/Views/Media/LinkView.swift deleted file mode 100644 index 8d685f753..000000000 --- a/Nos/Views/Media/LinkView.swift +++ /dev/null @@ -1,27 +0,0 @@ -import SwiftUI - -/// Displays a preview of content from a URL or an image, depending on the URL. -struct LinkView: View { - /// The URL of the content to display. - let url: URL - - var body: some View { - if url.isImage { - ImageButton(url: url) - } else { - LPLinkViewRepresentable(url: url) - } - } -} - -#Preview("Video") { - LinkView(url: URL(string: "https://youtu.be/5qvdbyRH9wA?si=y_KTgLR22nH0-cs8")!) -} - -#Preview("Image") { - LinkView( - url: URL( - string: "https://image.nostr.build/d5e38e7d864a344872d922d7f78daf67b0d304932fcb7fe22d35263c2fcf11c2.jpg" - )! - ) -} diff --git a/Nos/Views/Media/SquareImage.swift b/Nos/Views/Media/SquareImage.swift deleted file mode 100644 index dedefc72c..000000000 --- a/Nos/Views/Media/SquareImage.swift +++ /dev/null @@ -1,24 +0,0 @@ -import SwiftUI -import SDWebImageSwiftUI - -struct SquareImage: View { - var url: URL - - var onTap: (() -> Void)? - - var body: some View { - Color.clear - .aspectRatio(1, contentMode: .fit) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .overlay { - WebImage(url: url) - .resizable() - .indicator(.activity) - .aspectRatio(contentMode: .fill) - .onTapGesture { - onTap?() - } - } - .clipShape(Rectangle()) - } -} diff --git a/Nos/Views/Media/ZoomableContainer.swift b/Nos/Views/Media/ZoomableContainer.swift deleted file mode 100644 index b0aedae59..000000000 --- a/Nos/Views/Media/ZoomableContainer.swift +++ /dev/null @@ -1,123 +0,0 @@ -import SwiftUI - -/// A container that allows its content to be zoomed. -/// - Note: Thanks, [Ido](https://stackoverflow.com/users/8157190/ido) for your -/// [answer](https://stackoverflow.com/a/76649224) on StackOverflow! -struct ZoomableContainer: View { - let content: ContainerContent - private let maxAllowedScale = 4.0 - - @State private var currentScale: CGFloat = 1.0 - @State private var tapLocation: CGPoint = .zero - - init(@ViewBuilder content: () -> ContainerContent) { - self.content = content() - } - - func doubleTapAction(location: CGPoint) { - tapLocation = location - currentScale = currentScale == 1.0 ? maxAllowedScale : 1.0 - } - - var body: some View { - ZoomableScrollView(maxAllowedScale: maxAllowedScale, scale: $currentScale, tapLocation: $tapLocation) { - content - } - .onTapGesture(count: 2, perform: doubleTapAction) - } -} - -fileprivate struct ZoomableScrollView: UIViewRepresentable { - private var content: ScrollViewContent - private let maxAllowedScale: CGFloat - - @Binding private var currentScale: CGFloat - @Binding private var tapLocation: CGPoint - - init( - maxAllowedScale: CGFloat, - scale: Binding, - tapLocation: Binding, - @ViewBuilder content: () -> ScrollViewContent - ) { - self.maxAllowedScale = maxAllowedScale - _currentScale = scale - _tapLocation = tapLocation - self.content = content() - } - - func makeUIView(context: Context) -> UIScrollView { - // Setup the UIScrollView - let scrollView = UIScrollView() - scrollView.delegate = context.coordinator // for viewForZooming(in:) - scrollView.maximumZoomScale = maxAllowedScale - scrollView.minimumZoomScale = 1 - scrollView.bouncesZoom = true - scrollView.showsHorizontalScrollIndicator = false - scrollView.showsVerticalScrollIndicator = false - scrollView.clipsToBounds = false - - // Create a UIHostingController to hold our SwiftUI content - let hostedView = context.coordinator.hostingController.view! - hostedView.translatesAutoresizingMaskIntoConstraints = true - hostedView.autoresizingMask = [.flexibleWidth, .flexibleHeight] - hostedView.frame = scrollView.bounds - hostedView.backgroundColor = .clear - scrollView.addSubview(hostedView) - - return scrollView - } - - func makeCoordinator() -> Coordinator { - Coordinator(hostingController: UIHostingController(rootView: content), scale: $currentScale) - } - - func updateUIView(_ uiView: UIScrollView, context: Context) { - // Update the hosting controller's SwiftUI content - context.coordinator.hostingController.rootView = content - - if uiView.zoomScale > uiView.minimumZoomScale { // Scale out - uiView.setZoomScale(currentScale, animated: true) - } else if tapLocation != .zero { // Scale in to a specific point - uiView.zoom(to: zoomRect(for: uiView, scale: uiView.maximumZoomScale, center: tapLocation), animated: true) - // Reset the location to prevent scaling to it in case of a negative scale (manual pinch) - // Use the main thread to prevent unexpected behavior - DispatchQueue.main.async { tapLocation = .zero } - } - - assert(context.coordinator.hostingController.view.superview == uiView) - } - - // MARK: - Utils - - func zoomRect(for scrollView: UIScrollView, scale: CGFloat, center: CGPoint) -> CGRect { - let scrollViewSize = scrollView.bounds.size - - let width = scrollViewSize.width / scale - let height = scrollViewSize.height / scale - let xPosition = center.x - (width / 2.0) - let yPosition = center.y - (height / 2.0) - - return CGRect(x: xPosition, y: yPosition, width: width, height: height) - } - - // MARK: - Coordinator - - class Coordinator: NSObject, UIScrollViewDelegate { - var hostingController: UIHostingController - @Binding var currentScale: CGFloat - - init(hostingController: UIHostingController, scale: Binding) { - self.hostingController = hostingController - _currentScale = scale - } - - func viewForZooming(in scrollView: UIScrollView) -> UIView? { - hostingController.view - } - - func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) { - currentScale = scale - } - } -} From 363ee020818e8a23ae618c4d38e5c55a39f931de Mon Sep 17 00:00:00 2001 From: Josh Brown Date: Wed, 27 Nov 2024 13:07:04 -0500 Subject: [PATCH 30/30] refactor and animate! --- Nos/Views/Components/Media/GalleryView.swift | 52 +++++++++----------- 1 file changed, 22 insertions(+), 30 deletions(-) diff --git a/Nos/Views/Components/Media/GalleryView.swift b/Nos/Views/Components/Media/GalleryView.swift index fee9020cc..076328423 100644 --- a/Nos/Views/Components/Media/GalleryView.swift +++ b/Nos/Views/Components/Media/GalleryView.swift @@ -135,16 +135,36 @@ fileprivate struct GalleryIndexView: View { /// The maximum number of circles to display. private let maxNumberOfCircles = 7 + /// The range of indices to display. If there are too many pages, we only show up to `maxNumberOfCircles`. + private var displayRange: ClosedRange { + let radius = maxNumberOfCircles / 2 + let idealStart = currentIndex - radius + let idealEnd = currentIndex + radius + + switch (idealStart, idealEnd) { + case (...0, _): + // If ideal start is negative, pin to start of range + return 0...min(maxNumberOfCircles - 1, numberOfPages - 1) + case (_, numberOfPages...): + // If ideal end is greater than the number of pages, pin to end of range + return max(0, numberOfPages - maxNumberOfCircles)...numberOfPages - 1 + default: + // Ideal case - centered around current index + return idealStart...idealEnd + } + } + var body: some View { HStack(spacing: circleSpacing) { ForEach(0.. Bool { - if numberOfPages <= maxNumberOfCircles { - return true - } - - let expectedRange = currentIndex - maxNumberOfCircles / 2 ... currentIndex + maxNumberOfCircles / 2 - let realRange: ClosedRange - if expectedRange.lowerBound < 0 { - realRange = 0...expectedRange.upperBound - expectedRange.lowerBound - } else if expectedRange.upperBound >= numberOfPages { - realRange = expectedRange.lowerBound - (expectedRange.upperBound - numberOfPages + 1)...numberOfPages - 1 - } else { - realRange = expectedRange - } - - return realRange.contains(index) - } - - private func displayRange() -> ClosedRange { - let expectedRange = currentIndex - maxNumberOfCircles / 2 ... currentIndex + maxNumberOfCircles / 2 - let realRange: ClosedRange - if expectedRange.lowerBound < 0 { - realRange = 0...expectedRange.upperBound - expectedRange.lowerBound - } else if expectedRange.upperBound >= numberOfPages { - realRange = expectedRange.lowerBound - (expectedRange.upperBound - numberOfPages + 1)...numberOfPages - 1 - } else { - realRange = expectedRange - } - return realRange + displayRange.contains(index) } /// Calculates the scale factor for a circle at a given index. @@ -199,7 +192,6 @@ fileprivate struct GalleryIndexView: View { return 1.0 } - let displayRange = displayRange() if index == currentIndex { return 1.0 }