Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace GIFs with new GIF viewer #1437

Merged
merged 10 commits into from
Aug 29, 2024
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added a feature flag toggle for “Enable new media display” to Staging builds. [skip-release-notes]
- Added a new gallery view to display multiple links in a post. Currently behind the “Enable new media display” feature flag. [skip-release-notes]
- Show single images and gallery view in the proper orientation. Currently behind the “Enable new media display” feature flag. [skip-release-notes]
- Added an overlay to GIFs that plays the animation when tapped. Currently behind the “Enable new media display” feature flag. [skip-release-notes]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oooh, what is is [skip-release-notes]. Does this prevent them from showing up on TestFlight?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's the idea. Right now it's 100% manual; maybe in the future it'll be 100% automated. And maybe #1438 will change the approach slightly.

- Fixed typos in release notes. [skip-release-notes]

## [0.1.25] - 2024-08-21Z
Expand Down
2 changes: 1 addition & 1 deletion Nos.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -3233,7 +3233,7 @@
repositoryURL = "https://github.com/SDWebImage/SDWebImageSwiftUI";
requirement = {
kind = upToNextMajorVersion;
minimumVersion = 2.0.0;
minimumVersion = 3.1.2;
};
};
C9ADB139299299570075E7F8 /* XCRemoteSwiftPackageReference "bech32" */ = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/SDWebImage/SDWebImageSwiftUI",
"state" : {
"revision" : "53573d6dd017e354c0e7d8f1c86b77ef1383c996",
"version" : "2.2.7"
"revision" : "5aa947356f4ea49a0c3b9968564267f6ea5abea7",
"version" : "3.1.2"
}
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "0.800",
"blue" : "0x24",
"green" : "0x0E",
"red" : "0x16"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
11 changes: 11 additions & 0 deletions Nos/Assets/Localization/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -4914,6 +4914,17 @@
}
}
},
"gifButton" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "GIF"
}
}
}
},
"goBack" : {
"extractionState" : "manual",
"localizations" : {
Expand Down
18 changes: 16 additions & 2 deletions Nos/Extensions/URL+Extensions.swift
joshuatbrown marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,22 @@ extension URL {

return URL(string: "https://\(absoluteString)") ?? self
}


/// Returns `true` if the URL is an image, as determined by the path extension.
/// Currently supports `png`, `jpg`, `jpeg`, and `gif`. For all other path extensions, returns `false`.
var isImage: Bool {
let imageExtensions = ["png", "jpg", "jpeg", "gif"]
return imageExtensions.contains(pathExtension)
return imageExtensions.contains(pathExtension.lowercased())
}

/// Returns `true` if the URL is a GIF, as determined by the path extension.
var isGIF: Bool {
pathExtension.lowercased() == "gif"
}

/// Returns `absoluteString` but without a trailing slash if one exists. If no trailing slash exists, returns
/// `absoluteString` as-is.
/// - Returns: The absolute string of the URL without a trailing slash.
func strippingTrailingSlash() -> String {
var string = absoluteString
if string.last == "/" {
Expand All @@ -26,6 +36,10 @@ extension URL {
return string
}

/// Returns a Markdown-formatted link that can be used for display. The display portion of the Markdown string will
/// not contain the URL scheme, path, or path extension. In place of the path and path extension, an ellipsis will
/// appear. For example, with a URL like `https://www.nos.social/about`, this will return
/// `"[nos.social...](https://www.nos.social/about)"`.
var truncatedMarkdownLink: String {
let url = self.addingSchemeIfNeeded()

Expand Down
21 changes: 14 additions & 7 deletions Nos/Views/AvatarView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,24 @@ struct AvatarView: View {
var size: CGFloat

var body: some View {
WebImage(url: imageUrl)
.resizable()
.placeholder {
WebImage(
url: imageUrl,
content: { image in
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this an API change on SDWebImage? It stinks that we have to duplicate a bunch of the view modifiers now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, unfortunately the old API is gone. I can look and see if we can do anything better, or maybe try to unduplicate the view modifiers.

image
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: size, height: size)
.clipShape(Circle())
},
placeholder: {
Image.emptyAvatar
.resizable()
.renderingMode(.original)
.aspectRatio(contentMode: .fill)
.frame(width: size, height: size)
.clipShape(Circle())
}
.indicator(.activity)
.aspectRatio(contentMode: .fill)
.frame(width: size, height: size)
.clipShape(Circle())
)
}
}

Expand Down
42 changes: 39 additions & 3 deletions Nos/Views/Components/Media/ImageButton.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,54 @@ struct ImageButton: View {
/// Whether the image viewer is presented or not.
@State private var isViewerPresented = false

/// Whether the image is animating or not.
@State private var isAnimating = false

var body: some View {
Button {
isViewerPresented = true
} label: {
WebImage(url: url)
.resizable()
.scaledToFill()
ZStack {
WebImage(url: url, isAnimating: $isAnimating)
.resizable()
.scaledToFill()

if url.isGIF && !isAnimating {
gifOverlay
}
}
}
.sheet(isPresented: $isViewerPresented) {
ImageViewer(url: url)
}
}

var gifOverlay: some View {
Button {
isAnimating = true
} label: {
ZStack {
Color.clear

Text(.localizable.gifButton)
.font(.title)
.foregroundStyle(Color.primaryTxt)
.padding()
.background(
Circle()
.fill(Color.gifButtonBackground)
)
}
}
}
}

#Preview("GIF") {
ImageButton(
url: URL(
string: "https://image.nostr.build/c57c2e89841cf992626995271aa40571cdcf925b84af473d148595e577471d79.gif"
)!
)
}

#Preview {
Expand Down
67 changes: 58 additions & 9 deletions NosTests/Fixtures/URLExtensionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,37 +35,86 @@ final class URLExtensionTests: XCTestCase {
XCTAssertEqual(result, subject)
}

func testTruncatedMarkdownLink_withEmptyPathExtension() {
func test_isGIF_returns_false_for_jpg() throws {
let jpgURL = try XCTUnwrap(URL(string: "https://example.com/one.jpg"))
XCTAssertFalse(jpgURL.isGIF)
}

func test_isGIF_returns_true_for_gif() throws {
let gifURL = try XCTUnwrap(URL(string: "https://example.com/two.gif"))
XCTAssertTrue(gifURL.isGIF)
}

func test_isGIF_returns_true_for_GIF() throws {
let capitalizedGIFURL = try XCTUnwrap(URL(string: "https://example.com/three.GIF"))
XCTAssertTrue(capitalizedGIFURL.isGIF)
}

func test_isImage_returns_false_for_mp4() throws {
let url = try XCTUnwrap(URL(string: "http://example.com/test.mp4"))
XCTAssertFalse(url.isImage)
}

func test_isImage_returns_true_for_image_extensions() throws {
let pngURL = try XCTUnwrap(URL(string: "http://example.com/test.png"))
XCTAssertTrue(pngURL.isImage)

let jpegURL = try XCTUnwrap(URL(string: "http://example.com/test.jpeg"))
XCTAssertTrue(jpegURL.isImage)

let jpgURL = try XCTUnwrap(URL(string: "http://example.com/test.jpg"))
XCTAssertTrue(jpgURL.isImage)

let gifURL = try XCTUnwrap(URL(string: "http://example.com/test.gif"))
XCTAssertTrue(gifURL.isImage)
}

func test_isImage_returns_true_for_capitalized_path_extension() throws {
let url = try XCTUnwrap(URL(string: "http://example.com/one.PNG"))
XCTAssertTrue(url.isImage)
}

func test_strippingTrailingSlash_when_no_trailing_slash() throws {
let url = try XCTUnwrap(URL(string: "wss://relay.nos.social"))
XCTAssertEqual(url.strippingTrailingSlash(), "wss://relay.nos.social")
}

func test_strippingTrailingSlash_strips_trailing_slash() throws {
let url = try XCTUnwrap(URL(string: "wss://relay.nos.social/"))
XCTAssertEqual(url.strippingTrailingSlash(), "wss://relay.nos.social")
}

func test_truncatedMarkdownLink_withEmptyPathExtension() {
let url = URL(string: "https://subdomain.example.com")!
XCTAssertEqual(url.truncatedMarkdownLink, "[subdomain.example.com](https://subdomain.example.com)")
}

func testTruncatedMarkdownLink_withNonEmptyPathExtension() {
func test_truncatedMarkdownLink_withNonEmptyPathExtension() {
let url = URL(string: "https://example.com/image.png")!
XCTAssertEqual(url.truncatedMarkdownLink, "[example.com...](https://example.com/image.png)")
}

func testTruncatedMarkdownLink_withNilHost() {
func test_truncatedMarkdownLink_withNilHost() {
let url = URL(string: "nostr:1248904")!
XCTAssertEqual(url.truncatedMarkdownLink, "[nostr:1248904](nostr:1248904)")
}

func testTruncatedMarkdownLink_withWWW_removesWWW() {
func test_truncatedMarkdownLink_withWWW_removesWWW() {
let url = URL(string: "https://www.example.com")!
XCTAssertEqual(url.truncatedMarkdownLink, "[example.com](https://www.example.com)")
}

func testTruncatedMarkdownLink_noScheme_withWWW_removesWWW() {
func test_truncatedMarkdownLink_noScheme_withWWW_removesWWW() {
let url = URL(string: "www.nostr.com/get-started")!
XCTAssertEqual(url.truncatedMarkdownLink, "[nostr.com...](https://www.nostr.com/get-started)")
}

func testTruncatedMarkdownLink_noScheme_withWWW_noPath_doesNotIncludeEllipsis() {
let url = URL(string: "www.nos.social")!
XCTAssertEqual(url.truncatedMarkdownLink, "[nos.social](https://www.nos.social)")
func test_truncatedMarkdownLink_noScheme_withWWW_noPath_doesNotIncludeEllipsis() {
let url = URL(string: "https://www.nos.social/about")!
XCTAssertEqual(url.truncatedMarkdownLink, "[nos.social...](https://www.nos.social/about)")
}

func testTruncatedMarkdownLink_withShortPath() {
func test_truncatedMarkdownLink_withShortPath() {
let url = URL(string: "https://nips.be/1")!
XCTAssertEqual(url.truncatedMarkdownLink, "[nips.be...](https://nips.be/1)")
}
Expand Down
Loading