mirror of
git://jb55.com/damus
synced 2024-09-28 16:00:43 +00:00
Compare commits
6 Commits
07c504f701
...
5492d9f499
Author | SHA1 | Date | |
---|---|---|---|
|
5492d9f499 | ||
|
faec79d45d | ||
|
846a786fd0 | ||
|
517f3714e8 | ||
|
b733799567 | ||
|
db8dfc5edc |
35
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
35
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal 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.
|
27
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
27
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal 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.
|
@ -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 */,
|
||||
|
20
damus/Assets.xcassets/Colors/Bitcoin.colorset/Contents.json
Normal file
20
damus/Assets.xcassets/Colors/Bitcoin.colorset/Contents.json
Normal 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
|
||||
}
|
||||
}
|
@ -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")
|
||||
|
@ -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,33 +277,13 @@ 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
|
||||
PageControlView(currentPage: $model.selectedIndex, numberOfPages: urls.count)
|
||||
.frame(maxWidth: 0, maxHeight: 0)
|
||||
.padding(.top, 5)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.top, CGFloat(8))
|
||||
.id(UUID())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Image Modifier
|
||||
|
@ -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)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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?()
|
||||
|
64
damus/Views/Relays/Detail/RelayAdminDetail.swift
Normal file
64
damus/Views/Relays/Detail/RelayAdminDetail.swift
Normal 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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
79
damus/Views/Relays/Detail/RelayNipList.swift
Normal file
79
damus/Views/Relays/Detail/RelayNipList.swift
Normal 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])
|
||||
}
|
||||
}
|
@ -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 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: {
|
||||
Text(verbatim: "\(url)")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
57
damus/Views/Relays/Detail/RelaySoftwareDetail.swift
Normal file
57
damus/Views/Relays/Detail/RelaySoftwareDetail.swift
Normal 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)
|
||||
}
|
||||
}
|
@ -33,14 +33,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: {
|
||||
guard let ev = state.contacts.event else {
|
||||
@ -60,19 +52,16 @@ 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)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
ZStack {
|
||||
Group {
|
||||
Form {
|
||||
if let keypair = state.keypair.to_full() {
|
||||
if check_connection() {
|
||||
RemoveRelayButton(keypair)
|
||||
} else {
|
||||
func ConnectRelayButton(_ keypair: FullKeypair) -> some View {
|
||||
Button(action: {
|
||||
guard let ev_before_add = state.contacts.event else {
|
||||
return
|
||||
@ -88,76 +77,114 @@ struct RelayDetailView: View {
|
||||
}
|
||||
dismiss()
|
||||
}) {
|
||||
Text("Connect To Relay", comment: "Button to connect to the relay.")
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
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 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.")
|
||||
})
|
||||
if let nips = nip11.supported_nips, nips.count > 0 {
|
||||
RelayNipList(nips: nips)
|
||||
Divider()
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
if let keypair = state.keypair.to_full() {
|
||||
if check_connection() {
|
||||
RemoveRelayButton(keypair)
|
||||
.padding(.top)
|
||||
} else {
|
||||
ConnectRelayButton(keypair)
|
||||
.padding(.top)
|
||||
}
|
||||
}
|
||||
|
||||
if state.settings.developer_mode {
|
||||
Section(NSLocalizedString("Log", comment: "Label to display developer mode logs.")) {
|
||||
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,24 +196,12 @@ 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? {
|
||||
state.pool.get_relay(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)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user