Compare commits
1 Commits
quote-repo
...
render-blo
| Author | SHA1 | Date | |
|---|---|---|---|
|
3a331f6a05
|
48
CHANGELOG.md
48
CHANGELOG.md
@@ -1,51 +1,3 @@
|
||||
## [1.14] - 2025-05-25
|
||||
|
||||
### Added
|
||||
|
||||
- Added safety reminder to wallets with higher balance (Daniel D’Aquino)
|
||||
- Added one-click Coinos wallet setup (Daniel D’Aquino)
|
||||
- Add notification setting to hide hellthreads (Terry Yiu)
|
||||
- Added separated first aid option for relay lists that does not need a contact list reset (Daniel D’Aquino)
|
||||
- Added NIP-65 relay list support (Daniel D’Aquino)
|
||||
- Added Unicode 16 emoji reactions for iOS 18.4+ by upgrading EmojiPicker (Terry Yiu)
|
||||
- Added a search interface to the settings screen (SanjaySiddharth)
|
||||
- Added view introducing users to Zaps (ericholguin)
|
||||
- Added new wallet view with balance and transactions list (ericholguin)
|
||||
- Added copy technical info button to user visible errors, so that users can more easily share errors with developers (Daniel D’Aquino)
|
||||
- Add dismiss button to wallet high balance reminders (Daniel D’Aquino)
|
||||
- Zap receiver information now included for outgoing zaps (Daniel D’Aquino)
|
||||
- Added inline note rendering of invoices to pull up wallet selector sheet (Terry Yiu)
|
||||
- Added route to profile page from wallet tx list (ericholguin)
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
- Added additional information on top of blurred images (SanjaySiddharth)
|
||||
- Improved robustness of relay list handling (Daniel D’Aquino)
|
||||
- Updated image cache for better stability (Daniel D’Aquino)
|
||||
- Improved integration with Nostr Wallet Connect wallets (ericholguin)
|
||||
- Added relay connectivity information to NWC settings (Daniel D’Aquino)
|
||||
- Improved handling around NWC responses (Daniel D’Aquino)
|
||||
- Added more human visible errors on NWC wallets to aid with troubleshooting (Daniel D’Aquino)
|
||||
- Re-enabled note zaps as permitted by the new App Store guidelines (Daniel D’Aquino)
|
||||
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
- Hide future notes from timeline (Terry Yiu)
|
||||
- Fixed issue where profiles with a NIP-65 relay list would not display on Damus (Daniel D’Aquino)
|
||||
- Fix quote notes to include missing q tag (Terry Yiu)
|
||||
- Fixed issue where the side menu would close when copying the npub (SanjaySiddharth)
|
||||
- Fixed issue where cached images would be backed up to iCloud (Daniel D’Aquino)
|
||||
- Optimized classify_url function (Terry Yiu)
|
||||
- Fixed note rendering for those that contain previewable items or leading and trailing whitespaces (Terry Yiu)
|
||||
- Fixed issue where some videos would become unplayable after some time using the app (Daniel D’Aquino)
|
||||
|
||||
|
||||
[1.14]: https://github.com/damus-io/damus/releases/tag/v1.14
|
||||
|
||||
|
||||
## [1.13.1] - 2025-03-21
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -6419,7 +6419,7 @@
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 12.3;
|
||||
MARKETING_VERSION = 1.15;
|
||||
MARKETING_VERSION = 1.14;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
@@ -6484,7 +6484,7 @@
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
||||
MACOSX_DEPLOYMENT_TARGET = 12.3;
|
||||
MARKETING_VERSION = 1.15;
|
||||
MARKETING_VERSION = 1.14;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
|
||||
@@ -117,6 +117,10 @@ func render_blocks(blocks bs: Blocks, profiles: Profiles, can_hide_last_previewa
|
||||
}
|
||||
hide_text_index = i
|
||||
} else if case .text(let txt) = block, txt.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||
// We should hide whitespace at the end sequence.
|
||||
hide_text_index = i
|
||||
} else if case .hashtag = block {
|
||||
// We should keep hashtags at the end sequence but hide all the other previewables around it.
|
||||
hide_text_index = i
|
||||
} else {
|
||||
break
|
||||
@@ -149,7 +153,16 @@ func render_blocks(blocks bs: Blocks, profiles: Profiles, can_hide_last_previewa
|
||||
// No need to show the text representation of the block if the only previewables are the sequence of them
|
||||
// found at the end of the content.
|
||||
// This is to save unnecessary use of screen space.
|
||||
// The only exception is that if there are hashtags embedded in the end sequence, which is not uncommon,
|
||||
// then we still want to show those hashtags but hide everything else that is previewable in the end sequence.
|
||||
if ind >= hide_text_index {
|
||||
if case .text(let txt) = block, txt.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||
if case .hashtag = blocks[safe: ind+1] {
|
||||
return str + CompatibleText(stringLiteral: reduce_text_block(ind: ind, hide_text_index: -1, txt: txt))
|
||||
}
|
||||
} else if case .hashtag(let htag) = block {
|
||||
return str + hashtag_str(htag)
|
||||
}
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,6 @@ struct PostView: View {
|
||||
var autoSaveModel: AutoSaveIndicatorView.AutoSaveViewModel
|
||||
|
||||
@State var preUploadedMedia: [PreUploadedMedia] = []
|
||||
@State var mediaUploadUnderProgress: MediaUpload? = nil
|
||||
|
||||
@StateObject var image_upload: ImageUploadModel = ImageUploadModel()
|
||||
@StateObject var tagModel: TagModel = TagModel()
|
||||
@@ -331,6 +330,11 @@ struct PostView: View {
|
||||
PostButton
|
||||
}
|
||||
|
||||
if let progress = image_upload.progress {
|
||||
ProgressView(value: progress, total: 1.0)
|
||||
.progressViewStyle(.linear)
|
||||
}
|
||||
|
||||
Divider()
|
||||
.foregroundColor(DamusColors.neutral3)
|
||||
.padding(.top, 5)
|
||||
@@ -342,7 +346,6 @@ struct PostView: View {
|
||||
|
||||
@discardableResult
|
||||
func handle_upload(media: MediaUpload) async -> Bool {
|
||||
mediaUploadUnderProgress = media
|
||||
let uploader = damus_state.settings.default_media_uploader
|
||||
|
||||
let img = getImage(media: media)
|
||||
@@ -351,7 +354,6 @@ struct PostView: View {
|
||||
async let blurhash = calculate_blurhash(img: img)
|
||||
let res = await image_upload.start(media: media, uploader: uploader, mediaType: .normal, keypair: damus_state.keypair)
|
||||
|
||||
mediaUploadUnderProgress = nil
|
||||
switch res {
|
||||
case .success(let url):
|
||||
guard let url = URL(string: url) else {
|
||||
@@ -399,13 +401,10 @@ struct PostView: View {
|
||||
}
|
||||
.id("post")
|
||||
|
||||
PVImageCarouselView(media: $uploadedMedias,
|
||||
mediaUnderProgress: $mediaUploadUnderProgress,
|
||||
imageUploadModel: image_upload,
|
||||
deviceWidth: deviceSize.size.width)
|
||||
.onChange(of: uploadedMedias) { media in
|
||||
post_changed(post: post, media: media)
|
||||
}
|
||||
PVImageCarouselView(media: $uploadedMedias, deviceWidth: deviceSize.size.width)
|
||||
.onChange(of: uploadedMedias) { media in
|
||||
post_changed(post: post, media: media)
|
||||
}
|
||||
|
||||
if case .quoting(let ev) = action {
|
||||
BuilderEventView(damus: damus_state, event: ev)
|
||||
@@ -621,8 +620,6 @@ struct PostView_Previews: PreviewProvider {
|
||||
|
||||
struct PVImageCarouselView: View {
|
||||
@Binding var media: [UploadedMedia]
|
||||
@Binding var mediaUnderProgress: MediaUpload?
|
||||
@ObservedObject var imageUploadModel: ImageUploadModel
|
||||
|
||||
let deviceWidth: CGFloat
|
||||
|
||||
@@ -670,25 +667,6 @@ struct PVImageCarouselView: View {
|
||||
.padding(.bottom, 35)
|
||||
}
|
||||
}
|
||||
if let mediaUP = mediaUnderProgress, let progress = imageUploadModel.progress {
|
||||
ZStack {
|
||||
// Media under upload-progress
|
||||
Image(uiImage: getImage(media: mediaUP))
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(width: media.count == 0 ? deviceWidth * 0.8 : 250, height: media.count == 0 ? 400 : 250)
|
||||
.cornerRadius(10)
|
||||
.opacity(0.3)
|
||||
.padding()
|
||||
// Circle showing progress on top of media
|
||||
Circle()
|
||||
.trim(from: 0, to: CGFloat(progress))
|
||||
.stroke(Color.damusPurple, lineWidth: 5.0)
|
||||
.rotationEffect(.degrees(-90))
|
||||
.frame(width: 30, height: 30)
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
|
||||
@@ -12,19 +12,9 @@ struct QuoteRepostsView: View {
|
||||
@ObservedObject var model: EventsModel
|
||||
|
||||
var body: some View {
|
||||
TimelineView(events: model.events, loading: $model.loading, damus: damus_state, show_friend_icon: true, filter: ContentFilters.default_filters(damus_state: damus_state).filter(ev:)) {
|
||||
ZStack(alignment: .leading) {
|
||||
DamusBackground(maxHeight: 250)
|
||||
.mask(LinearGradient(gradient: Gradient(colors: [.black, .black, .black, .clear]), startPoint: .top, endPoint: .bottom))
|
||||
Text("Quotes", comment: "Navigation bar title for Quote Reposts view.")
|
||||
.foregroundStyle(DamusLogoGradient.gradient)
|
||||
.font(.title.bold())
|
||||
.padding(.leading, 30)
|
||||
.padding(.top, 30)
|
||||
}
|
||||
}
|
||||
.ignoresSafeArea()
|
||||
TimelineView<AnyView>(events: model.events, loading: $model.loading, damus: damus_state, show_friend_icon: true, filter: ContentFilters.default_filters(damus_state: damus_state).filter(ev:))
|
||||
.padding(.bottom, tabHeight)
|
||||
.navigationBarTitle(NSLocalizedString("Quotes", comment: "Navigation bar title for Quote Reposts view."))
|
||||
.onAppear {
|
||||
model.subscribe()
|
||||
}
|
||||
|
||||
@@ -291,6 +291,45 @@ class NoteContentViewTests: XCTestCase {
|
||||
XCTAssertEqual(noteArtifactsSeparated.images[0].absoluteString, "https://damus.io/nothidden.png")
|
||||
}
|
||||
|
||||
func testRenderBlocksWithPreviewableBlocksAtEndAreHiddenWhenHashtagsAreEmbedded() throws {
|
||||
let noteId = test_note.id.bech32
|
||||
let invoiceString = "lnbc100n1p357sl0sp5t9n56wdztun39lgdqlr30xqwksg3k69q4q2rkr52aplujw0esn0qpp5mrqgljk62z20q4nvgr6lzcyn6fhylzccwdvu4k77apg3zmrkujjqdpzw35xjueqd9ejqcfqv3jhxcmjd9c8g6t0dcxqyjw5qcqpjrzjqt56h4gvp5yx36u2uzqa6qwcsk3e2duunfxppzj9vhypc3wfe2wswz607uqq3xqqqsqqqqqqqqqqqlqqyg9qyysgqagx5h20aeulj3gdwx3kxs8u9f4mcakdkwuakasamm9562ffyr9en8yg20lg0ygnr9zpwp68524kmda0t5xp2wytex35pu8hapyjajxqpsql29r"
|
||||
let content = " Check this out. \nhttps://hidden.tld/\nhttps://damus.io/hidden1.png\n\(invoiceString)\nhttps://damus.io/hidden2.png\nnostr:\(noteId)#hashtag1 #hashtag2 "
|
||||
let note = try XCTUnwrap(NostrEvent(content: content, keypair: test_keypair))
|
||||
let parsed: Blocks = parse_note_content(content: .init(note: note, keypair: test_keypair))
|
||||
|
||||
let testState = test_damus_state
|
||||
|
||||
let noteArtifactsSeparated: NoteArtifactsSeparated = render_blocks(blocks: parsed, profiles: testState.profiles, can_hide_last_previewable_refs: true)
|
||||
let attributedText: AttributedString = noteArtifactsSeparated.content.attributed
|
||||
|
||||
let runs: AttributedString.Runs = attributedText.runs
|
||||
let runArray: [AttributedString.Runs.Run] = Array(runs)
|
||||
XCTAssertEqual(runArray.count, 4)
|
||||
XCTAssertTrue(runArray[0].description.contains("Check this out."))
|
||||
XCTAssertFalse(runArray[0].description.contains("https://hidden.tld/"))
|
||||
XCTAssertFalse(runArray[0].description.contains("https://damus.io/hidden1.png"))
|
||||
XCTAssertFalse(runArray[0].description.contains("lnbc100n:qpsql29r"))
|
||||
XCTAssertFalse(runArray[0].description.contains("https://damus.io/hidden2.png"))
|
||||
XCTAssertFalse(runArray[0].description.contains("note1qqq:qqn2l0z3"))
|
||||
XCTAssertTrue(runArray[1].description.contains("#hashtag1"))
|
||||
XCTAssertTrue(runArray[3].description.contains("#hashtag2"))
|
||||
|
||||
XCTAssertEqual(noteArtifactsSeparated.images.count, 2)
|
||||
XCTAssertEqual(noteArtifactsSeparated.images[0].absoluteString, "https://damus.io/hidden1.png")
|
||||
XCTAssertEqual(noteArtifactsSeparated.images[1].absoluteString, "https://damus.io/hidden2.png")
|
||||
|
||||
XCTAssertEqual(noteArtifactsSeparated.media.count, 2)
|
||||
XCTAssertEqual(noteArtifactsSeparated.media[0].url.absoluteString, "https://damus.io/hidden1.png")
|
||||
XCTAssertEqual(noteArtifactsSeparated.media[1].url.absoluteString, "https://damus.io/hidden2.png")
|
||||
|
||||
XCTAssertEqual(noteArtifactsSeparated.links.count, 1)
|
||||
XCTAssertEqual(noteArtifactsSeparated.links[0].absoluteString, "https://hidden.tld/")
|
||||
|
||||
XCTAssertEqual(noteArtifactsSeparated.invoices.count, 1)
|
||||
XCTAssertEqual(noteArtifactsSeparated.invoices[0].string, invoiceString)
|
||||
}
|
||||
|
||||
/// Based on https://github.com/damus-io/damus/issues/1468
|
||||
/// Tests whether a note content view correctly parses an image block when url in JSON content contains optional escaped slashes
|
||||
func testParseImageBlockInContentWithEscapedSlashes() throws {
|
||||
|
||||
Reference in New Issue
Block a user