-
Notifications
You must be signed in to change notification settings - Fork 296
gif: Nostr Build GIF Keyboard #2387
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
{ | ||
"images" : [ | ||
{ | ||
"filename" : "nb-logo_nb-logo-color.svg", | ||
"idiom" : "universal" | ||
} | ||
], | ||
"info" : { | ||
"author" : "xcode", | ||
"version" : 1 | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
// | ||
// NostrBuildGIF.swift | ||
// damus | ||
// | ||
// Created by eric on 8/14/24. | ||
// | ||
|
||
import Foundation | ||
|
||
let pageSize: Int = 30 | ||
|
||
func makeGIFRequest(cursor: Int) async throws -> NostrBuildGIFResponse { | ||
var request = URLRequest(url: URL(string: String(format: "https://nostr.build/api/v2/gifs/get?cursor=%d&limit=%d&random=%d", | ||
cursor, | ||
pageSize, | ||
0))!) | ||
|
||
request.httpMethod = "GET" | ||
request.setValue("application/json", forHTTPHeaderField: "Content-Type") | ||
|
||
let response: NostrBuildGIFResponse = try await decodedData(for: request) | ||
return response | ||
} | ||
|
||
private func decodedData<Output: Decodable>(for request: URLRequest) async throws -> Output { | ||
let decoder = JSONDecoder() | ||
let session = URLSession.shared | ||
let (data, response) = try await session.data(for: request) | ||
|
||
if let httpResponse = response as? HTTPURLResponse { | ||
switch httpResponse.statusCode { | ||
case 200: | ||
let result = try decoder.decode(Output.self, from: data) | ||
return result | ||
default: | ||
Log.error("Error retrieving gif data from Nostr Build. HTTP status code: %d; Response: %s", for: .gif_request, httpResponse.statusCode, String(data: data, encoding: .utf8) ?? "Unknown") | ||
throw NostrBuildError.http_response_error(status_code: httpResponse.statusCode, response: data) | ||
} | ||
} | ||
|
||
throw NostrBuildError.could_not_process_response | ||
} | ||
|
||
enum NostrBuildError: Error { | ||
case http_response_error(status_code: Int, response: Data) | ||
case could_not_process_response | ||
} | ||
|
||
struct NostrBuildGIFResponse: Codable { | ||
let status: String | ||
let message: String | ||
let cursor: Int | ||
let count: Int | ||
let gifs: [NostrBuildGif] | ||
} | ||
|
||
struct NostrBuildGif: Codable, Identifiable { | ||
var id: String { bh } | ||
var url: String | ||
/// This is the blurhash of the gif that can be used as an ID and placeholder | ||
let bh: String | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
// | ||
// NostrBuildGIFGrid.swift | ||
// damus | ||
// | ||
// Created by eric on 8/14/24. | ||
// | ||
|
||
import SwiftUI | ||
import Kingfisher | ||
|
||
|
||
struct NostrBuildGIFGrid: View { | ||
let damus_state: DamusState | ||
@State var results:[NostrBuildGif] = [] | ||
@State var cursor: Int = 0 | ||
@State var errorAlert: Bool = false | ||
@SceneStorage("NostrBuildGIFGrid.show_nsfw_alert") var show_nsfw_alert : Bool = true | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need to persist There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes this way users are not presented with the alert again. |
||
@SceneStorage("NostrBuildGIFGrid.persist_nsfw_alert") var persist_nsfw_alert : Bool = true | ||
@Environment(\.dismiss) var dismiss | ||
|
||
var onSelect:(String) -> () | ||
|
||
let columns = [ | ||
GridItem(.flexible()), | ||
GridItem(.flexible()), | ||
GridItem(.flexible()) | ||
] | ||
|
||
var TopBar: some View { | ||
VStack { | ||
HStack(spacing: 5.0) { | ||
|
||
Button(action: { | ||
Task { | ||
cursor -= pageSize | ||
do { | ||
let response = try await makeGIFRequest(cursor: cursor) | ||
self.results = response.gifs | ||
} catch { | ||
print(error.localizedDescription) | ||
} | ||
} | ||
}, label: { | ||
Text("Back", comment: "Button to go to previous page.") | ||
.padding(10) | ||
}) | ||
.buttonStyle(NeutralButtonStyle()) | ||
.opacity(cursor > 0 ? 1 : 0) | ||
.disabled(cursor == 0) | ||
|
||
Spacer() | ||
|
||
Image("nostrbuild") | ||
.resizable() | ||
.frame(width: 40, height: 40) | ||
|
||
Spacer() | ||
|
||
Button(NSLocalizedString("Next", comment: "Button to go to next page.")) { | ||
Task { | ||
cursor += pageSize | ||
do { | ||
let response = try await makeGIFRequest(cursor: cursor) | ||
self.results = response.gifs | ||
} catch { | ||
print(error.localizedDescription) | ||
} | ||
} | ||
} | ||
.bold() | ||
.buttonStyle(GradientButtonStyle(padding: 10)) | ||
} | ||
|
||
Divider() | ||
.foregroundColor(DamusColors.neutral3) | ||
.padding(.top, 5) | ||
} | ||
.frame(height: 30) | ||
.padding() | ||
.padding(.top, 15) | ||
} | ||
|
||
var body: some View { | ||
VStack { | ||
TopBar | ||
ScrollView { | ||
LazyVGrid(columns: columns, spacing: 5) { | ||
ForEach($results) { gifResult in | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Personal opinion/optional comment: Instead of having This helps differentiate the UI when there are no results vs when we are still loading results. It also simplifies state management, as an error on the NostrBuild request is interconnected with the GIF results There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That makes sense, I might need some help with that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can help with that if you need anything, just let me know what you would need help with! |
||
VStack { | ||
if let url = URL(string: gifResult.url.wrappedValue) { | ||
ZStack { | ||
KFAnimatedImage(url) | ||
.imageContext(.note, disable_animation: damus_state.settings.disable_animation) | ||
.cancelOnDisappear(true) | ||
.configure { view in | ||
view.framePreloadCount = 3 | ||
} | ||
.clipShape(RoundedRectangle(cornerRadius: 12.0)) | ||
.frame(width: 120, height: 120) | ||
.aspectRatio(contentMode: .fill) | ||
.onTapGesture { | ||
onSelect(url.absoluteString) | ||
dismiss() | ||
} | ||
if persist_nsfw_alert { | ||
Blur() | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
Spacer() | ||
} | ||
} | ||
.padding() | ||
.alert("Error", isPresented: $errorAlert) { | ||
Button(NSLocalizedString("OK", comment: "Exit this view")) { | ||
dismiss() | ||
} | ||
} message: { | ||
Text("Failed to load GIFs") | ||
} | ||
.alert("NSFW", isPresented: $show_nsfw_alert) { | ||
Button(NSLocalizedString("Cancel", comment: "Exit this view")) { | ||
dismiss() | ||
} | ||
Button(NSLocalizedString("Proceed", comment: "Button to continue")) { | ||
show_nsfw_alert = false | ||
persist_nsfw_alert = false | ||
} | ||
} message: { | ||
Text("NSFW means \"Not Safe For Work\". The content in this view may be inappropriate to view in some situations and may contain explicit images.", comment: "Warning to the user that there may be content that is not safe for work.") | ||
} | ||
.onAppear { | ||
Task { | ||
await initial() | ||
} | ||
if persist_nsfw_alert { | ||
show_nsfw_alert = true | ||
} | ||
} | ||
} | ||
|
||
func initial() async { | ||
do { | ||
let response = try await makeGIFRequest(cursor: cursor) | ||
self.results = response.gifs | ||
} catch { | ||
print(error) | ||
errorAlert = true | ||
} | ||
|
||
} | ||
} | ||
|
||
struct NostrBuildGIFGrid_Previews: PreviewProvider { | ||
static var previews: some View { | ||
NostrBuildGIFGrid(damus_state: test_damus_state) { gifURL in | ||
print("GIF URL: \(gifURL)") | ||
} | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.