Merge commit 'f0d242a'

This commit is contained in:
William Casarin
2022-12-21 09:46:03 -08:00
13 changed files with 283 additions and 27 deletions

View File

@@ -0,0 +1,77 @@
//
// Shimmer.swift
//
//
// Created by Joshua Homann on 2/20/21.
//
import SwiftUI
public struct ShimmerConfiguration {
@Environment(\.colorScheme) var colorScheme
public let gradient: Gradient
public let initialLocation: (start: UnitPoint, end: UnitPoint)
public let finalLocation: (start: UnitPoint, end: UnitPoint)
public let duration: TimeInterval
public let opacity: Double
public static let `default` = ShimmerConfiguration(
gradient: Gradient(stops: [
.init(color: .clear, location: 0),
.init(color: .black, location: 0.3),
.init(color: .black, location: 0.7),
.init(color: .clear, location: 1),
]),
initialLocation: (start: UnitPoint(x: -1, y: 0.5), end: .leading),
finalLocation: (start: .trailing, end: UnitPoint(x: 2, y: 0.5)),
duration: 2,
opacity: 0.6
)
}
struct ShimmeringView<Content: View>: View {
private let content: () -> Content
private let configuration: ShimmerConfiguration
@State private var startPoint: UnitPoint
@State private var endPoint: UnitPoint
init(configuration: ShimmerConfiguration, @ViewBuilder content: @escaping () -> Content) {
self.configuration = configuration
self.content = content
_startPoint = .init(wrappedValue: configuration.initialLocation.start)
_endPoint = .init(wrappedValue: configuration.initialLocation.end)
}
var body: some View {
ZStack {
content()
LinearGradient(
gradient: configuration.gradient,
startPoint: startPoint,
endPoint: endPoint
)
.opacity(configuration.opacity)
.blendMode(.overlay)
.onAppear {
withAnimation(Animation.linear(duration: configuration.duration).repeatForever(autoreverses: false)) {
startPoint = configuration.finalLocation.start
endPoint = configuration.finalLocation.end
}
}
}
.edgesIgnoringSafeArea(.all)
}
}
public struct ShimmerModifier: ViewModifier {
let configuration: ShimmerConfiguration
public func body(content: Content) -> some View {
ShimmeringView(configuration: configuration) { content }
}
}
public extension View {
func shimmer(configuration: ShimmerConfiguration = .default) -> some View {
modifier(ShimmerModifier(configuration: configuration))
}
}

View File

@@ -549,3 +549,4 @@ func setup_notifications() {
}
}
}

View File

@@ -0,0 +1,25 @@
//
// Constants.swift
// damus
//
// Created by Sam DuBois on 12/18/22.
//
import Foundation
public class Constants {
static let PUB_KEY = "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"
static let EXAMPLE_DEMOS = DamusState(pool: RelayPool(), keypair: Keypair(pubkey: PUB_KEY, privkey: "privkey"), likes: EventCounter(our_pubkey: PUB_KEY), boosts: EventCounter(our_pubkey: PUB_KEY), contacts: Contacts(), tips: TipCounter(our_pubkey: PUB_KEY), profiles: Profiles(), dms: DirectMessagesModel())
static let EXAMPLE_EVENTS = [
NostrEvent(content: "Icecream", pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"),
NostrEvent(content: "This is a test for a really long note that somebody sent because they thought they were super cool or maybe they were just really excited to share something with the world.", pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"),
NostrEvent(content: "Bonjour Le Monde", pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"),
NostrEvent(content: "Why am I helping on this app? Because it's fun!", pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"),
NostrEvent(content: "PIzza", pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"),
NostrEvent(content: "Hello World! This is so cool!", pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"),
NostrEvent(content: "Nostr - Damus... Haha get it?", pubkey: "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"),
]
}

View File

@@ -44,7 +44,7 @@ struct EventActionBar: View {
HStack(alignment: .bottom) {
Text("\(bar.boosts > 0 ? "\(bar.boosts)" : "")")
.font(.footnote)
.font(.footnote.weight(.medium))
.foregroundColor(bar.boosted ? Color.green : Color.gray)
EventActionButton(img: "arrow.2.squarepath", col: bar.boosted ? Color.green : nil) {
@@ -58,7 +58,7 @@ struct EventActionBar: View {
HStack(alignment: .bottom) {
Text("\(bar.likes > 0 ? "\(bar.likes)" : "")")
.font(.footnote)
.font(.footnote.weight(.medium))
.foregroundColor(bar.liked ? Color.red : Color.gray)
EventActionButton(img: bar.liked ? "heart.fill" : "heart", col: bar.liked ? Color.red : nil) {
@@ -140,7 +140,7 @@ struct EventActionBar: View {
func EventActionButton(img: String, col: Color?, action: @escaping () -> ()) -> some View {
Button(action: action) {
Label("", systemImage: img)
.font(.footnote)
.font(.footnote.weight(.medium))
.foregroundColor(col == nil ? Color.gray : col!)
}
.padding(.trailing, 40)

View File

@@ -83,17 +83,22 @@ struct EventView: View {
NavigationLink(destination: booster_profile) {
HStack {
Label("", systemImage: "arrow.2.squarepath")
Image(systemName: "arrow.2.squarepath")
.font(.footnote.weight(.bold))
.foregroundColor(Color.gray)
ProfileName(pubkey: event.pubkey, profile: damus.profiles.lookup(id: event.pubkey), contacts: damus.contacts, show_friend_confirmed: show_friend_icon)
.foregroundColor(Color.gray)
Text(" Boosted")
if let prof = damus.profiles.lookup(id: event.pubkey) {
Text(Profile.displayName(profile: prof, pubkey: event.pubkey))
.font(.footnote.weight(.bold))
.foregroundColor(Color.gray)
}
Text("Boosted")
.font(.footnote.weight(.bold))
.foregroundColor(Color.gray)
}
}
.buttonStyle(PlainButtonStyle())
TextEvent(inner_ev, pubkey: inner_ev.pubkey)
.padding([.top], 2)
.padding([.top], 1)
}
} else {
TextEvent(event, pubkey: pubkey)
@@ -119,11 +124,12 @@ struct EventView: View {
VStack(alignment: .leading) {
HStack(alignment: .center) {
ProfileName(pubkey: pubkey, profile: profile, contacts: damus.contacts, show_friend_confirmed: show_friend_icon)
EventProfileName(pubkey: pubkey, profile: profile, contacts: damus.contacts, show_friend_confirmed: show_friend_icon)
Text("\(format_relative_time(event.created_at))")
.font(.body)
.foregroundColor(.gray)
}
if event.is_reply(damus.keypair.privkey) {
Text("\(reply_desc(profiles: damus.profiles, event: event))")
.font(.footnote)

View File

@@ -8,6 +8,9 @@
import SwiftUI
struct FollowButtonView: View {
@Environment(\.colorScheme) var colorScheme
let target: FollowTarget
@State var follow_state: FollowState
@@ -16,15 +19,15 @@ struct FollowButtonView: View {
follow_state = perform_follow_btn_action(follow_state, target: target)
} label: {
Text(follow_btn_txt(follow_state))
.padding(.horizontal, 20)
.padding(.vertical, 7)
.padding(.horizontal, 25)
.padding(.vertical, 10)
.font(.caption.weight(.bold))
.foregroundColor(follow_state == .unfollows ? .white : .black)
.background(follow_state == .unfollows ? .black : .white)
.foregroundColor(follow_state == .unfollows ? emptyColor() : fillColor())
.background(follow_state == .unfollows ? fillColor() : emptyColor())
.cornerRadius(20)
.overlay {
RoundedRectangle(cornerRadius: 16)
.stroke(follow_state == .unfollows ? .white : .gray, lineWidth: 1)
.stroke(follow_state == .unfollows ? .clear : borderColor(), lineWidth: 1)
}
}
.onReceive(handle_notify(.followed)) { notif in
@@ -44,6 +47,18 @@ struct FollowButtonView: View {
self.follow_state = .unfollows
}
}
func fillColor() -> Color {
colorScheme == .light ? .black : .white
}
func emptyColor() -> Color {
colorScheme == .light ? .white : .black
}
func borderColor() -> Color {
colorScheme == .light ? .black.opacity(0.1) : .white.opacity(0.2)
}
}
struct FollowButtonPreviews: View {

View File

@@ -64,8 +64,10 @@ struct NoteContentView: View {
return VStack(alignment: .leading) {
if let txt = try? AttributedString(markdown: artifacts.content, options: md_opts) {
Text(txt)
.font(.body)
} else {
Text(artifacts.content)
.font(.body)
}
if show_images && artifacts.images.count > 0 {
ImageCarousel(urls: artifacts.images)

View File

@@ -23,7 +23,7 @@ struct ProfileFullName: View {
.font(.footnote)
.foregroundColor(.gray)
} else {
ProfileName(pubkey: pubkey, profile: profile, contacts: contacts, show_friend_confirmed: true)
// ProfileName(pubkey: pubkey, profile: profile, contacts: contacts, show_friend_confirmed: true)
}
}
}
@@ -73,8 +73,9 @@ struct ProfileName: View {
var body: some View {
HStack {
Text(prefix + String(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey)))
//.foregroundColor(hex_to_rgb(pubkey))
.font(.body)
.fontWeight(prefix == "@" ? .none : .bold)
if let frend = friend_icon {
Label("", systemImage: frend)
@@ -92,4 +93,76 @@ struct ProfileName: View {
}
}
/// Profile Name used when displaying an event in the timeline
struct EventProfileName: View {
let pubkey: String
let profile: Profile?
let contacts: Contacts
let prefix: String
let show_friend_confirmed: Bool
@State var display_name: String?
init(pubkey: String, profile: Profile?, contacts: Contacts, show_friend_confirmed: Bool) {
self.pubkey = pubkey
self.profile = profile
self.prefix = ""
self.contacts = contacts
self.show_friend_confirmed = show_friend_confirmed
}
init(pubkey: String, profile: Profile?, prefix: String, contacts: Contacts, show_friend_confirmed: Bool) {
self.pubkey = pubkey
self.profile = profile
self.prefix = prefix
self.contacts = contacts
self.show_friend_confirmed = show_friend_confirmed
}
var friend_icon: String? {
if !show_friend_confirmed {
return nil
}
if self.contacts.is_friend(self.pubkey) {
return "person.fill.checkmark"
}
if self.contacts.is_friend_of_friend(self.pubkey) {
return "person.fill.and.arrow.left.and.arrow.right"
}
return nil
}
var body: some View {
HStack {
if let real_name = profile?.display_name {
Text(real_name)
.font(.body.weight(.bold))
Text("@" + String(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey)))
.foregroundColor(.gray)
.font(.body)
} else {
Text(String(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey)))
.font(.body)
.fontWeight(.bold)
}
if let frend = friend_icon {
Label("", systemImage: frend)
.foregroundColor(.gray)
.font(.footnote)
}
}
.onReceive(handle_notify(.profile_updated)) { notif in
let update = notif.object as! ProfileUpdate
if update.pubkey != pubkey {
return
}
display_name = Profile.displayName(profile: update.profile, pubkey: pubkey)
}
}
}

View File

@@ -95,8 +95,8 @@ struct ProfileView: View {
}) {
Image(systemName: "bolt.circle")
.symbolRenderingMode(.palette)
.foregroundStyle(colorScheme == .dark ? .white : .black, .gray)
.font(.system(size: 27).weight(.thin))
.font(.system(size: 34).weight(.thin))
.foregroundStyle(colorScheme == .light ? .black : .white, colorScheme == .light ? .black.opacity(0.1) : .white.opacity(0.2))
}
}
@@ -107,8 +107,8 @@ struct ProfileView: View {
return NavigationLink(destination: dmview) {
Image(systemName: "bubble.left.circle")
.symbolRenderingMode(.palette)
.font(.system(size: 29).weight(.thin))
.foregroundStyle(colorScheme == .dark ? .white : .black, .gray)
.font(.system(size: 34).weight(.thin))
.foregroundStyle(colorScheme == .light ? .black : .white, colorScheme == .light ? .black.opacity(0.1) : .white.opacity(0.2))
}
}
@@ -134,6 +134,7 @@ struct ProfileView: View {
.padding(.bottom)
Text(data?.about ?? "")
.font(.subheadline)
Divider()
@@ -144,7 +145,9 @@ struct ProfileView: View {
NavigationLink(destination: FollowingView(damus_state: damus_state, following: following_model, whos: profile.pubkey)) {
HStack {
Text("\(profile.following)")
.font(.subheadline.weight(.medium))
Text("Following")
.font(.subheadline)
.foregroundColor(.gray)
}
}
@@ -155,7 +158,9 @@ struct ProfileView: View {
NavigationLink(destination: fview) {
HStack {
Text("\(followers.contacts.count)")
.font(.subheadline.weight(.medium))
Text("Followers")
.font(.subheadline)
.foregroundColor(.gray)
}
}

View File

@@ -40,11 +40,19 @@ struct SearchHomeView: View {
}
var GlobalContent: some View {
TimelineView(events: $model.events, loading: $model.loading, damus: damus_state, show_friend_icon: true, filter: { _ in true })
return TimelineView(events: $model.events, loading: $model.loading, damus: damus_state, show_friend_icon: true, filter: { _ in true })
.refreshable {
// Fetch new information by resubscribing to the relay
model.subscribe()
}
}
var SearchContent: some View {
SearchResultsView(damus_state: damus_state, search: $search)
.refreshable {
// Fetch new information by resubscribing to the relay
model.subscribe()
}
}
var MainContent: some View {

View File

@@ -40,7 +40,27 @@ struct InnerTimelineView: View {
}
}
struct InnerTimelineRedactedView: View {
let events: [NostrEvent]
let damus: DamusState
let show_friend_icon: Bool
var body: some View {
VStack {
ForEach(events, id: \.id) { event in
EventView(event: event, highlight: .none, has_action_bar: true, damus: damus, show_friend_icon: show_friend_icon)
.buttonStyle(PlainButtonStyle())
}
}
.shimmer()
.redacted(reason: .placeholder)
.padding(.horizontal)
.disabled(true)
}
}
struct TimelineView: View {
@Binding var events: [NostrEvent]
@Binding var loading: Bool
@@ -56,6 +76,7 @@ struct TimelineView: View {
ScrollViewReader { scroller in
ScrollView {
if loading {
InnerTimelineRedactedView(events: Constants.EXAMPLE_EVENTS, damus: damus, show_friend_icon: true)
ProgressView()
.progressViewStyle(.circular)
} else {
@@ -72,13 +93,11 @@ struct TimelineView: View {
}
}
/*
struct TimelineView_Previews: PreviewProvider {
static var previews: some View {
TimelineView()
TimelineView(events: .constant(Constants.EXAMPLE_EVENTS), loading: .constant(true), damus: Constants.EXAMPLE_DEMOS, show_friend_icon: true, filter: { _ in true })
}
}
*/
struct NavigationLazyView<Content: View>: View {
@@ -90,4 +109,3 @@ struct NavigationLazyView<Content: View>: View {
build()
}
}