Skip to content

Commit

Permalink
Merge pull request #1437 from planetary-social/gif-viewer
Browse files Browse the repository at this point in the history
Replace GIFs with new GIF viewer
  • Loading branch information
joshuatbrown authored Aug 29, 2024
2 parents e48c2ee + 509a7a2 commit 15f9971
Show file tree
Hide file tree
Showing 9 changed files with 162 additions and 24 deletions.
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]
- 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
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
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

0 comments on commit 15f9971

Please sign in to comment.