1
0
mirror of git://jb55.com/damus synced 2024-09-28 16:00:43 +00:00

Compare commits

...

6 Commits

Author SHA1 Message Date
ericholguin
5492d9f499 ux: Relay Detail Redesign
This patch redesigns the relay detail view. The first step needed to
further improve how relays are viewed. In addition, Fees are added to
the relay metadata to present to the user the admission, subscription,
or publication fees a relay may have.

There are various changes made, but mainly several relay details are
moved outside of the main RelayDetail view for easier tracking and
updates.

With this design we will be able to add ratings and relay previews, as
this patch is large enough I will submit that improvement in the future.

iPhone 15 Pro Max (17.3.1) Dark Mode:
https://i.nostr.build/VwVvq.png
https://i.nostr.build/KGO0v.png

iPhone SE (3rd generation) (16.4) Light Mode:
https://i.nostr.build/M5V26.png
https://i.nostr.build/gZKw3.png

Changelog-Added: Relay fees metadata
Changelog-Changed: Relay detail design
Signed-off-by: ericholguin <ericholguin@apache.org>
Signed-off-by: William Casarin <jb55@jb55.com>
2024-04-04 10:33:28 -07:00
ericholguin
faec79d45d ui: fix cut off emoji reaction
This patch is a simple fix to emoji reactions being cut off.

Fixes: https://github.com/damus-io/damus/issues/1728
Changelog-Fixed: Fix emoji reactions being cut off
Signed-off-by: ericholguin <ericholguin@apache.org>
Signed-off-by: William Casarin <jb55@jb55.com>
2024-04-04 10:33:28 -07:00
ericholguin
846a786fd0 ux: fix image indicators
Changelog-Fixed: Fix image indicators to limit number of dots to not spill screen beyond visible margins
Closes: https://github.com/damus-io/damus/issues/1227
Closes: https://github.com/damus-io/damus/issues/1295
Signed-off-by: ericholguin <ericholguin@apache.org>
Signed-off-by: William Casarin <jb55@jb55.com>
2024-04-04 10:33:28 -07:00
Sean Kibler
517f3714e8 postview: add haptic feedback on media upload result
Closes: https://github.com/damus-io/damus/pull/2115
Signed-off-by: Sean Kibler <skibler@protonmail.com>
Signed-off-by: William Casarin <jb55@jb55.com>
2024-04-04 10:33:28 -07:00
alltheseas
b733799567
Update issue templates
Added feature request template
2024-03-25 10:12:13 -05:00
alltheseas
db8dfc5edc
Update issue templates
Modified issue bug report template.
2024-03-25 10:08:51 -05:00
16 changed files with 620 additions and 165 deletions

35
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,35 @@
---
name: Bug report
about: Create a report to help us improve
title: 'Bug: '
labels: bug, Needs recreation
assignees: ''
---
**What happens**
When I perform action ___, _____ happens.
**What I expect to happen**
I expect _______ to happen.
**Link to noteID, npub**
Provide link to relevant noteID, npub etc.
**Screenshots/video recording**
If applicable, add screenshots to help explain your problem.
** Versions **
Damus version: [e.g. 1.7.2 (1()]
Operating system version: [e.g. iOS 17.2.1]
Device: e.g. iPhone 13 Pro
**Steps To Reproduce**
Steps to reproduce the behavior:
1. Open Damus
2. Tap on ___
3. Action ____
**Additional context**
Add any other context about the problem here.

View File

@ -0,0 +1,27 @@
---
name: Feature request
about: Suggest an idea for this project
title: 'Feature Request:'
labels: feature
assignees: ''
---
## Have a go at filling out the User Story template below
As a Damus user who is _____________, I would like to _________________, so that I achieve ___________.
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
** When does this problem happen? **
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@ -395,6 +395,9 @@
50C3E08A2AA8E3F7006A4BC0 /* AVPlayer+Additions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C3E0892AA8E3F7006A4BC0 /* AVPlayer+Additions.swift */; };
50DA11262A16A23F00236234 /* Launch.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 50DA11252A16A23F00236234 /* Launch.storyboard */; };
5C0707D12A1ECB38004E7B51 /* DamusLogoGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C0707D02A1ECB38004E7B51 /* DamusLogoGradient.swift */; };
5C14C29B2BBBA29C00079FD2 /* RelaySoftwareDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C14C29A2BBBA29C00079FD2 /* RelaySoftwareDetail.swift */; };
5C14C29D2BBBA40B00079FD2 /* RelayAdminDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C14C29C2BBBA40B00079FD2 /* RelayAdminDetail.swift */; };
5C14C29F2BBBA5C600079FD2 /* RelayNipList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C14C29E2BBBA5C600079FD2 /* RelayNipList.swift */; };
5C42E78C29DB76D90086AAC1 /* EmptyUserSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C42E78B29DB76D90086AAC1 /* EmptyUserSearchView.swift */; };
5C513FBA297F72980072348F /* CustomPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C513FB9297F72980072348F /* CustomPicker.swift */; };
5C513FCC2984ACA60072348F /* QRCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C513FCB2984ACA60072348F /* QRCodeView.swift */; };
@ -1316,6 +1319,9 @@
50C3E0892AA8E3F7006A4BC0 /* AVPlayer+Additions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AVPlayer+Additions.swift"; sourceTree = "<group>"; };
50DA11252A16A23F00236234 /* Launch.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Launch.storyboard; sourceTree = "<group>"; };
5C0707D02A1ECB38004E7B51 /* DamusLogoGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusLogoGradient.swift; sourceTree = "<group>"; };
5C14C29A2BBBA29C00079FD2 /* RelaySoftwareDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelaySoftwareDetail.swift; sourceTree = "<group>"; };
5C14C29C2BBBA40B00079FD2 /* RelayAdminDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayAdminDetail.swift; sourceTree = "<group>"; };
5C14C29E2BBBA5C600079FD2 /* RelayNipList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayNipList.swift; sourceTree = "<group>"; };
5C42E78B29DB76D90086AAC1 /* EmptyUserSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyUserSearchView.swift; sourceTree = "<group>"; };
5C513FB9297F72980072348F /* CustomPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPicker.swift; sourceTree = "<group>"; };
5C513FCB2984ACA60072348F /* QRCodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeView.swift; sourceTree = "<group>"; };
@ -2576,6 +2582,9 @@
children = (
4CE879542996BAB900F758CC /* RelayPaidDetail.swift */,
B57B4C632B312BFA00A232C0 /* RelayAuthenticationDetail.swift */,
5C14C29A2BBBA29C00079FD2 /* RelaySoftwareDetail.swift */,
5C14C29C2BBBA40B00079FD2 /* RelayAdminDetail.swift */,
5C14C29E2BBBA5C600079FD2 /* RelayNipList.swift */,
);
path = Detail;
sourceTree = "<group>";
@ -3205,6 +3214,7 @@
4CC7AAEB297F0AEC00430951 /* BuilderEventView.swift in Sources */,
E02429952B7E97740088B16C /* CameraController.swift in Sources */,
31D2E847295218AF006D67F8 /* Shimmer.swift in Sources */,
5C14C29F2BBBA5C600079FD2 /* RelayNipList.swift in Sources */,
D7CB5D3E2B116DAD00AD4105 /* NotificationsManager.swift in Sources */,
50A16FFF2AA76A0900DFEC1F /* VideoController.swift in Sources */,
F7908E97298B1FDF00AB113A /* NIPURLBuilder.swift in Sources */,
@ -3359,6 +3369,7 @@
3CCD1E6A2A874C4E0099A953 /* Nip98HTTPAuth.swift in Sources */,
4C8EC52529D1FA6C0085D9A8 /* DamusColors.swift in Sources */,
3A4647CF2A413ADC00386AD8 /* CondensedProfilePicturesView.swift in Sources */,
5C14C29B2BBBA29C00079FD2 /* RelaySoftwareDetail.swift in Sources */,
D2277EEA2A089BD5006C3807 /* Router.swift in Sources */,
3A90B1812A4EA3AF00000D94 /* UserSearchCache.swift in Sources */,
4C9D6D162B1AA9C6004E5CD9 /* DisplayTabBarNotify.swift in Sources */,
@ -3386,6 +3397,7 @@
4C3BEFD42819DE8F00B3DE84 /* NostrKind.swift in Sources */,
B533694E2B66D791008A805E /* MutelistManager.swift in Sources */,
4C32B9532A9AD44700DC3548 /* Verifier.swift in Sources */,
5C14C29D2BBBA40B00079FD2 /* RelayAdminDetail.swift in Sources */,
4C3EA66028FF5E7700C48A62 /* node_id.c in Sources */,
4C687C212A5F7ED00092C550 /* DamusBackground.swift in Sources */,
4CA352A02A76AE80003BB08B /* Notify.swift in Sources */,

View File

@ -0,0 +1,20 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x1A",
"green" : "0x93",
"red" : "0xF7"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -24,6 +24,7 @@ class DamusColors {
static let purple = Color("DamusPurple")
static let deepPurple = Color("DamusDeepPurple")
static let blue = Color("DamusBlue")
static let bitcoin = Color("Bitcoin")
static let success = Color("DamusSuccessPrimary")
static let successSecondary = Color("DamusSuccessSecondary")
static let successTertiary = Color("DamusSuccessTertiary")

View File

@ -31,6 +31,49 @@ struct ShareSheet: UIViewControllerRepresentable {
}
}
// Custom UIPageControl
struct PageControlView: UIViewRepresentable {
@Binding var currentPage: Int
var numberOfPages: Int
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> UIPageControl {
let uiView = UIPageControl()
uiView.backgroundStyle = .minimal
uiView.currentPageIndicatorTintColor = UIColor(Color("DamusPurple"))
uiView.pageIndicatorTintColor = UIColor(Color("DamusLightGrey"))
uiView.currentPage = currentPage
uiView.numberOfPages = numberOfPages
uiView.addTarget(context.coordinator, action: #selector(Coordinator.valueChanged), for: .valueChanged)
return uiView
}
func updateUIView(_ uiView: UIPageControl, context: Context) {
uiView.currentPage = currentPage
uiView.numberOfPages = numberOfPages
}
}
extension PageControlView {
final class Coordinator: NSObject {
var parent: PageControlView
init(_ parent: PageControlView) {
self.parent = parent
}
@objc func valueChanged(sender: UIPageControl) {
let currentPage = sender.currentPage
withAnimation {
parent.currentPage = currentPage
}
}
}
}
enum ImageShape {
case square
@ -227,7 +270,6 @@ struct ImageCarousel<Content: View>: View {
.onChange(of: model.selectedIndex) { value in
model.selectedIndex = value
}
.tabViewStyle(PageTabViewStyle())
}
var body: some View {
@ -235,31 +277,11 @@ struct ImageCarousel<Content: View>: View {
Medias
.onTapGesture { }
// This is our custom carousel image indicator
CarouselDotsView(urls: urls, selectedIndex: $model.selectedIndex)
}
}
}
// MARK: - Custom Carousel
struct CarouselDotsView<T>: View {
let urls: [T]
@Binding var selectedIndex: Int
var body: some View {
if urls.count > 1 {
HStack {
ForEach(urls.indices, id: \.self) { index in
Circle()
.fill(index == selectedIndex ? Color("DamusPurple") : Color("DamusLightGrey"))
.frame(width: 10, height: 10)
.onTapGesture {
selectedIndex = index
}
}
if urls.count > 1 {
PageControlView(currentPage: $model.selectedIndex, numberOfPages: urls.count)
.frame(maxWidth: 0, maxHeight: 0)
.padding(.top, 5)
}
.padding(.top, CGFloat(8))
.id(UUID())
}
}
}

View File

@ -56,9 +56,20 @@ class ImageUploadModel: NSObject, URLSessionTaskDelegate, ObservableObject {
func start(media: MediaUpload, uploader: MediaUploader, keypair: Keypair? = nil) async -> ImageUploadResult {
let res = await create_upload_request(mediaToUpload: media, mediaUploader: uploader, progress: self, keypair: keypair)
DispatchQueue.main.async {
self.progress = nil
switch res {
case .success(_):
DispatchQueue.main.async {
self.progress = nil
UINotificationFeedbackGenerator().notificationOccurred(.success)
}
case .failed(_):
DispatchQueue.main.async {
self.progress = nil
UINotificationFeedbackGenerator().notificationOccurred(.error)
}
}
return res
}

View File

@ -84,6 +84,33 @@ struct Limitations: Codable {
}
}
struct Admission: Codable {
let amount: Int64
let unit: String
}
struct Subscription: Codable {
let amount: Int64
let unit: String
let period: Int
}
struct Publication: Codable {
let kinds: [Int]
let amount: Int64
let unit: String
}
struct Fees: Codable {
let admission: [Admission]?
let subscription: [Subscription]?
let publication: [Publication]?
static var empty: Fees {
Fees(admission: nil, subscription: nil, publication: nil)
}
}
struct RelayMetadata: Codable {
let name: String?
let description: String?
@ -95,6 +122,7 @@ struct RelayMetadata: Codable {
let limitation: Limitations?
let payments_url: String?
let icon: String?
let fees: Fees?
var is_paid: Bool {
return limitation?.payment_required ?? false

View File

@ -205,12 +205,12 @@ struct LikeButton: View {
Group {
if let liked_emoji {
buildMaskView(for: liked_emoji)
.frame(width: 20, height: 20)
.frame(width: 22, height: 20)
} else {
Image("shaka")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 20, height: 20)
.frame(width: 22, height: 20)
.foregroundColor(.gray)
}
}

View File

@ -37,21 +37,6 @@ struct FullScreenCarouselView<Content: View>: View {
self.content = nil
}
var tabViewIndicator: some View {
HStack(spacing: 10) {
ForEach(urls.indices, id: \.self) { index in
Capsule()
.fill(index == selectedIndex ? Color.white : Color.damusMediumGrey)
.frame(width: 7, height: 7)
.onTapGesture {
selectedIndex = index
}
}
}
.padding()
.clipShape(Capsule())
}
var background: some ShapeStyle {
if case .video = urls[safe: selectedIndex] {
return AnyShapeStyle(Color.black)
@ -115,8 +100,10 @@ struct FullScreenCarouselView<Content: View>: View {
.foregroundColor(.white)
Spacer()
if (urls.count > 1) {
tabViewIndicator
if urls.count > 1 {
PageControlView(currentPage: $selectedIndex, numberOfPages: urls.count)
.frame(maxWidth: 0, maxHeight: 0)
.padding(.top, 5)
}
self.content?()

View File

@ -0,0 +1,64 @@
//
// RelayAdminDetail.swift
// damus
//
// Created by eric on 4/1/24.
//
import SwiftUI
struct RelayAdminDetail: View {
let state: DamusState
let nip11: RelayMetadata?
var body: some View {
HStack(spacing: 15) {
VStack(spacing: 10) {
Text("ADMIN")
.font(.caption)
.fontWeight(.heavy)
.foregroundColor(DamusColors.mediumGrey)
if let pubkey = nip11?.pubkey {
ProfilePicView(pubkey: pubkey, size: 40, highlight: .custom(.gray.opacity(0.5), 1), profiles: state.profiles, disable_animation: state.settings.disable_animation)
.padding(.bottom, 5)
.onTapGesture {
state.nav.push(route: Route.ProfileByKey(pubkey: pubkey))
}
} else {
Image("user-circle")
.resizable()
.frame(width: 50, height: 50)
.foregroundColor(.gray.opacity(0.5))
}
}
Divider().frame(width: 1)
VStack {
Text("CONTACT")
.font(.caption)
.fontWeight(.heavy)
.foregroundColor(DamusColors.mediumGrey)
Image("messages")
.foregroundColor(.gray)
if nip11?.contact == "" {
Text("N/A")
.font(.subheadline)
.foregroundColor(.gray)
} else {
Text(nip11?.contact ?? "N/A")
.font(.subheadline)
.foregroundColor(.gray)
}
}
}
}
}
struct RelayAdminDetail_Previews: PreviewProvider {
static var previews: some View {
let metadata = RelayMetadata(name: "name", description: "Relay description", pubkey: test_pubkey, contact: "contact@mail.com", supported_nips: [1,2,3], software: "software", version: "version", limitation: Limitations.empty, payments_url: "https://jb55.com", icon: "", fees: Fees.empty)
RelayAdminDetail(state: test_damus_state, nip11: metadata)
}
}

View File

@ -16,12 +16,41 @@ struct RelayAuthenticationDetail: View {
EmptyView()
case .pending:
Text(NSLocalizedString("Pending", comment: "Label to display that authentication to a server is pending."))
.font(.caption)
.frame(height: 20)
.padding(.horizontal, 10)
.foregroundColor(DamusColors.warning)
.background(DamusColors.warningQuaternary)
.cornerRadius(20)
.overlay(
RoundedRectangle(cornerRadius: 20)
.stroke(DamusColors.warningBorder, lineWidth: 1)
)
case .verified:
Text(NSLocalizedString("Authenticated", comment: "Label to display that authentication to a server has succeeded."))
.foregroundStyle(DamusColors.success)
.font(.caption)
.frame(height: 20)
.padding(.horizontal, 10)
.foregroundColor(DamusColors.success)
.background(DamusColors.successQuaternary)
.cornerRadius(20)
.overlay(
RoundedRectangle(cornerRadius: 20)
.stroke(DamusColors.successBorder, lineWidth: 1)
)
case .error:
Text(NSLocalizedString("Error", comment: "Label to display that authentication to a server has failed."))
.foregroundStyle(DamusColors.danger)
.font(.caption)
.frame(height: 20)
.padding(.horizontal, 10)
.foregroundColor(DamusColors.danger)
.background(DamusColors.dangerQuaternary)
.border(DamusColors.dangerBorder)
.cornerRadius(20)
.overlay(
RoundedRectangle(cornerRadius: 20)
.stroke(DamusColors.dangerBorder, lineWidth: 1)
)
}
}
}

View File

@ -0,0 +1,79 @@
//
// RelayNipList.swift
// damus
//
// Created by eric on 4/1/24.
//
import SwiftUI
struct NIPNumber: View {
let character: String
var body: some View {
NIPIcon {
Text(verbatim: character)
.font(.title3.bold())
.mask(Text(verbatim: character)
.font(.title3.bold()))
}
}
}
struct NIPIcon<Content: View>: View {
let content: Content
init(@ViewBuilder content: () -> Content) {
self.content = content()
}
var body: some View {
ZStack {
Circle()
.fill(DamusColors.neutral3)
.frame(width: 40, height: 40)
content
.foregroundStyle(DamusColors.mediumGrey)
}
}
}
struct RelayNipList: View {
let nips: [Int]
@Environment(\.openURL) var openURL
var body: some View {
VStack(alignment: .leading, spacing: 10) {
Text(NSLocalizedString("Supported NIPs", comment: "Label to display relay's supported NIPs."))
.font(.callout)
.fontWeight(.bold)
.foregroundColor(DamusColors.mediumGrey)
ScrollView(.horizontal) {
HStack {
ForEach(nips, id:\.self) { nip in
if let link = NIPURLBuilder.url(forNIP: nip) {
let nipString = NIPURLBuilder.formatNipNumber(nip: nip)
Button(action: {
openURL(link)
}) {
NIPNumber(character: "\(nipString)")
}
}
}
}
}
.padding(.bottom)
.scrollIndicators(.hidden)
}
}
}
struct RelayNipList_Previews: PreviewProvider {
static var previews: some View {
RelayNipList(nips: [0, 1, 2, 3, 4, 11, 15, 50])
}
}

View File

@ -9,18 +9,78 @@ import SwiftUI
struct RelayPaidDetail: View {
let payments_url: String?
var fees: Fees? = nil
@Environment(\.openURL) var openURL
var body: some View {
func timeString(time: Int) -> String {
let formatter = DateComponentsFormatter()
formatter.allowedUnits = [.year, .month, .day, .hour, .minute, .second]
formatter.unitsStyle = .full
let formattedString = formatter.string(from: TimeInterval(time)) ?? ""
return formattedString
}
func Amount(unit: String, amount: Int64) -> some View {
HStack {
RelayType(is_paid: true)
if let url = payments_url.flatMap({ URL(string: $0) }) {
Button(action: {
openURL(url)
}, label: {
Text(verbatim: "\(url)")
})
if unit == "msats" {
Text("\(format_msats(amount))")
.font(.system(size: 13, weight: .heavy))
.foregroundColor(DamusColors.white)
} else {
Text("\(amount) \(unit)")
.font(.system(size: 13, weight: .heavy))
.foregroundColor(DamusColors.white)
}
}
}
var body: some View {
HStack(spacing: 0) {
ZStack(alignment: .leading) {
if let url = payments_url.flatMap({ URL(string: $0) }) {
RelayType(is_paid: true)
.zIndex(1)
Button(action: {
openURL(url)
}, label: {
if let admission = fees?.admission {
if !admission.isEmpty {
Amount(unit: admission[0].unit, amount: admission[0].amount)
} else {
Text(verbatim: "Paid Relay")
.font(.system(size: 13, weight: .heavy))
.foregroundColor(DamusColors.white)
}
} else if let subscription = fees?.subscription {
if !subscription.isEmpty {
Amount(unit: subscription[0].unit, amount: subscription[0].amount)
Text("/ \(timeString(time: subscription[0].period))")
.font(.system(size: 13, weight: .heavy))
.foregroundColor(DamusColors.white)
}
} else if let publication = fees?.publication {
if !publication.isEmpty {
Amount(unit: publication[0].unit, amount: publication[0].amount)
Text("/ event")
.font(.system(size: 13, weight: .heavy))
.foregroundColor(DamusColors.white)
}
} else {
Text(verbatim: "Paid Relay")
.font(.system(size: 13, weight: .heavy))
.foregroundColor(DamusColors.white)
}
})
.padding(EdgeInsets(top: 3, leading: 25, bottom: 3, trailing: 10))
.background(DamusColors.bitcoin.opacity(0.7))
.cornerRadius(15)
.overlay(
RoundedRectangle(cornerRadius: 20)
.stroke(DamusColors.warningBorder, lineWidth: 1)
)
.padding(.leading, 1)
}
}
}
}
@ -28,6 +88,10 @@ struct RelayPaidDetail: View {
struct RelayPaidDetail_Previews: PreviewProvider {
static var previews: some View {
RelayPaidDetail(payments_url: "https://jb55.com")
let admission = Admission(amount: 1000000, unit: "msats")
let sub = Subscription(amount: 5000000, unit: "msats", period: 2592000)
let pub = Publication(kinds: [1, 4], amount: 100, unit: "msats")
let fees = Fees(admission: [admission], subscription: [sub], publication: [pub])
RelayPaidDetail(payments_url: "https://jb55.com", fees: fees)
}
}

View File

@ -0,0 +1,57 @@
//
// RelaySoftwareDetail.swift
// damus
//
// Created by eric on 4/1/24.
//
import SwiftUI
struct RelaySoftwareDetail: View {
let nip11: RelayMetadata?
var body: some View {
HStack(spacing: 15) {
VStack {
Text("SOFTWARE")
.font(.caption)
.fontWeight(.heavy)
.foregroundColor(DamusColors.mediumGrey)
Image("code")
.foregroundColor(.gray)
let software = nip11?.software
let softwareSeparated = software?.components(separatedBy: "/")
let softwareShortened = softwareSeparated?.last
Text(softwareShortened ?? "N/A")
.font(.subheadline)
.foregroundColor(.gray)
}
Divider().frame(width: 1)
VStack {
Text("VERSION")
.font(.caption)
.fontWeight(.heavy)
.foregroundColor(DamusColors.mediumGrey)
Image("branches")
.foregroundColor(.gray)
Text(nip11?.version ?? "N/A")
.font(.subheadline)
.foregroundColor(.gray)
}
}
}
}
struct RelaySoftwareDetail_Previews: PreviewProvider {
static var previews: some View {
let metadata = RelayMetadata(name: "name", description: "desc", pubkey: test_pubkey, contact: "contact", supported_nips: [1,2,3], software: "git+https://github.com/hoytech/strfry.git", version: "0.9.6-26-gc0dec7c", limitation: Limitations.empty, payments_url: "https://jb55.com", icon: "", fees: Fees.empty)
RelaySoftwareDetail(nip11: metadata)
}
}

View File

@ -32,14 +32,6 @@ struct RelayDetailView: View {
}
return false
}
func FieldText(_ str: String?) -> some View {
if let s = str {
return Text(verbatim: s)
} else {
return Text("No data available", comment: "Text indicating that there is no data available to show for specific metadata about a relay server.")
}
}
func RemoveRelayButton(_ keypair: FullKeypair) -> some View {
Button(action: {
@ -60,104 +52,139 @@ struct RelayDetailView: View {
}
dismiss()
}) {
Text("Disconnect From Relay", comment: "Button to disconnect from the relay.")
HStack {
Text("Disconnect", comment: "Button to disconnect from the relay.")
.fontWeight(.semibold)
}
.frame(minWidth: 300, maxWidth: .infinity, alignment: .center)
}
.buttonStyle(NeutralButtonShape.rounded.style)
}
func ConnectRelayButton(_ keypair: FullKeypair) -> some View {
Button(action: {
guard let ev_before_add = state.contacts.event else {
return
}
guard let ev_after_add = add_relay(ev: ev_before_add, keypair: keypair, current_relays: state.pool.our_descriptors, relay: relay, info: .rw) else {
return
}
process_contact_event(state: state, ev: ev_after_add)
state.postbox.send(ev_after_add)
if let relay_metadata = make_relay_metadata(relays: state.pool.our_descriptors, keypair: keypair) {
state.postbox.send(relay_metadata)
}
dismiss()
}) {
HStack {
Text("Connect", comment: "Button to connect to the relay.")
.fontWeight(.semibold)
}
.frame(minWidth: 300, maxWidth: .infinity, alignment: .center)
}
.buttonStyle(NeutralButtonShape.rounded.style)
}
var RelayInfo: some View {
ScrollView(.horizontal) {
Group {
HStack(spacing: 15) {
RelayAdminDetail(state: state, nip11: nip11)
Divider().frame(width: 1)
RelaySoftwareDetail(nip11: nip11)
}
}
}
.scrollIndicators(.hidden)
}
var RelayHeader: some View {
HStack(alignment: .top, spacing: 15) {
RelayPicView(relay: relay, icon: nip11?.icon, size: 90, highlight: .none, disable_animation: false)
VStack(alignment: .leading) {
Text(nip11?.name ?? relay.absoluteString)
.font(.title)
.fontWeight(.bold)
.lineLimit(1)
Text(relay.absoluteString)
.font(.headline)
.fontWeight(.regular)
.foregroundColor(.gray)
HStack {
if nip11?.is_paid ?? false {
RelayPaidDetail(payments_url: nip11?.payments_url, fees: nip11?.fees)
}
if let authentication_state: RelayAuthenticationState = relay_object?.authentication_state,
authentication_state != .none {
RelayAuthenticationDetail(state: authentication_state)
}
}
}
}
}
var body: some View {
NavigationView {
ZStack {
Group {
Form {
Group {
ScrollView {
Divider()
VStack(alignment: .leading, spacing: 10) {
RelayHeader
Divider()
Text("Description")
.font(.subheadline)
.foregroundColor(DamusColors.mediumGrey)
Text(nip11?.description ?? "N/A")
.font(.subheadline)
Divider()
RelayInfo
Divider()
if let nip11 {
if let nips = nip11.supported_nips, nips.count > 0 {
RelayNipList(nips: nips)
Divider()
}
}
if let keypair = state.keypair.to_full() {
if check_connection() {
RemoveRelayButton(keypair)
.padding(.top)
} else {
Button(action: {
guard let ev_before_add = state.contacts.event else {
return
}
guard let ev_after_add = add_relay(ev: ev_before_add, keypair: keypair, current_relays: state.pool.our_descriptors, relay: relay, info: .rw) else {
return
}
process_contact_event(state: state, ev: ev_after_add)
state.postbox.send(ev_after_add)
if let relay_metadata = make_relay_metadata(relays: state.pool.our_descriptors, keypair: keypair) {
state.postbox.send(relay_metadata)
}
dismiss()
}) {
Text("Connect To Relay", comment: "Button to connect to the relay.")
}
}
}
if let authentication_state: RelayAuthenticationState = relay_object?.authentication_state,
authentication_state != .none {
Section(NSLocalizedString("Authentication", comment: "Header label to display authentication details for a given relay.")) {
RelayAuthenticationDetail(state: authentication_state)
}
}
if let pubkey = nip11?.pubkey {
Section(NSLocalizedString("Admin", comment: "Label to display relay contact user.")) {
UserViewRow(damus_state: state, pubkey: pubkey)
.onTapGesture {
state.nav.push(route: Route.ProfileByKey(pubkey: pubkey))
}
}
}
if let relay_connection {
Section(NSLocalizedString("Relay", comment: "Label to display relay address.")) {
HStack {
Text(relay.absoluteString)
Spacer()
RelayStatusView(connection: relay_connection)
}
}
}
if let nip11 {
if nip11.is_paid {
Section(content: {
RelayPaidDetail(payments_url: nip11.payments_url)
}, header: {
Text("Paid Relay", comment: "Section header that indicates the relay server requires payment.")
}, footer: {
Text("This is a paid relay, you must pay for notes to be accepted.", comment: "Footer description that explains that the relay server requires payment to post.")
})
}
Section(NSLocalizedString("Description", comment: "Label to display relay description.")) {
FieldText(nip11.description)
}
Section(NSLocalizedString("Contact", comment: "Label to display relay contact information.")) {
FieldText(nip11.contact)
}
Section(NSLocalizedString("Software", comment: "Label to display relay software.")) {
FieldText(nip11.software)
}
Section(NSLocalizedString("Version", comment: "Label to display relay software version.")) {
FieldText(nip11.version)
}
if let nips = nip11.supported_nips, nips.count > 0 {
Section(NSLocalizedString("Supported NIPs", comment: "Label to display relay's supported NIPs.")) {
Text(nipsList(nips: nips))
}
ConnectRelayButton(keypair)
.padding(.top)
}
}
if state.settings.developer_mode {
Section(NSLocalizedString("Log", comment: "Label to display developer mode logs.")) {
Text(log.contents ?? NSLocalizedString("No logs to display", comment: "Label to indicate that there are no developer mode logs available to be displayed on the screen"))
.font(.system(size: 13))
.lineLimit(nil)
.fixedSize(horizontal: false, vertical: true)
}
Text("Relay Logs")
.padding(.top)
Divider()
Text(log.contents ?? NSLocalizedString("No logs to display", comment: "Label to indicate that there are no developer mode logs available to be displayed on the screen"))
.font(.system(size: 13))
.lineLimit(nil)
.fixedSize(horizontal: false, vertical: true)
}
}
.padding(.horizontal)
}
}
}
@ -169,23 +196,11 @@ struct RelayDetailView: View {
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: BackNav())
.ignoresSafeArea(.all)
}
private func nipsList(nips: [Int]) -> AttributedString {
var attrString = AttributedString()
let lastNipIndex = nips.count - 1
for (index, nip) in nips.enumerated() {
if let link = NIPURLBuilder.url(forNIP: nip) {
let nipString = NIPURLBuilder.formatNipNumber(nip: nip)
var nipAttrString = AttributedString(stringLiteral: nipString)
nipAttrString.link = link
attrString = attrString + nipAttrString
if index < lastNipIndex {
attrString = attrString + AttributedString(stringLiteral: ", ")
}
.toolbar {
if let relay_connection {
RelayStatusView(connection: relay_connection)
}
}
return attrString
}
private var relay_object: Relay? {
@ -199,7 +214,11 @@ struct RelayDetailView: View {
struct RelayDetailView_Previews: PreviewProvider {
static var previews: some View {
let metadata = RelayMetadata(name: "name", description: "desc", pubkey: test_pubkey, contact: "contact", supported_nips: [1,2,3], software: "software", version: "version", limitation: Limitations.empty, payments_url: "https://jb55.com", icon: "")
let admission = Admission(amount: 1000000, unit: "msats")
let sub = Subscription(amount: 5000000, unit: "msats", period: 2592000)
let pub = Publication(kinds: [4], amount: 100, unit: "msats")
let fees = Fees(admission: [admission], subscription: [sub], publication: [pub])
let metadata = RelayMetadata(name: "name", description: "Relay description", pubkey: test_pubkey, contact: "contact@mail.com", supported_nips: [1,2,3], software: "software", version: "version", limitation: Limitations.empty, payments_url: "https://jb55.com", icon: "", fees: fees)
RelayDetailView(state: test_damus_state, relay: RelayURL("wss://relay.damus.io")!, nip11: metadata)
}
}