refactor: Adding structure

Huge refactor to add better structure to the project.
Separating features with their associated view and model structure.
This should be better organization and will allow us to improve the
overall architecture in the future.

I forsee many more improvements that can follow this change. e.g. MVVM Arch
As well as cleaning up duplicate, unused, functionality.
Many files have global functions that can also be moved or be renamed.

damus/
├── Features/
│   ├── <Feature>/
│   │   ├── Views/
│   │   └── Models/
├── Shared/
│   ├── Components/
│   ├── Media/
│   ├── Buttons/
│   ├── Extensions/
│   ├── Empty Views/
│   ├── ErrorHandling/
│   ├── Modifiers/
│   └── Utilities/
├── Core/
│   ├── Nostr/
│   ├── NIPs/
│   ├── DIPs/
│   ├── Types/
│   ├── Networking/
│   └── Storage/

Signed-off-by: ericholguin <ericholguin@apache.org>
This commit is contained in:
ericholguin
2025-07-22 19:36:18 -06:00
committed by Daniel D’Aquino
parent fdbf271432
commit 65a22813a3
427 changed files with 936 additions and 548 deletions

View File

@@ -0,0 +1,42 @@
//
// QuoteRepostsView.swift
// damus
//
// Created by William Casarin on 2024-03-16.
//
import SwiftUI
struct QuoteRepostsView: View {
let damus_state: DamusState
@ObservedObject var model: EventsModel
var body: some View {
TimelineView(events: model.events, loading: $model.loading, damus: damus_state, show_friend_icon: true, filter: ContentFilters.default_filters(damus_state: damus_state).filter(ev:)) {
ZStack(alignment: .leading) {
DamusBackground(maxHeight: 250)
.mask(LinearGradient(gradient: Gradient(colors: [.black, .black, .black, .clear]), startPoint: .top, endPoint: .bottom))
Text("Quotes", comment: "Navigation bar title for Quote Reposts view.")
.foregroundStyle(DamusLogoGradient.gradient)
.font(.title.bold())
.padding(.leading, 30)
.padding(.top, 30)
}
}
.ignoresSafeArea()
.padding(.bottom, tabHeight)
.onAppear {
model.subscribe()
}
.onDisappear {
model.unsubscribe()
}
}
}
struct QuoteRepostsView_Previews: PreviewProvider {
static var previews: some View {
let state = test_damus_state
QuoteRepostsView(damus_state: state, model: .reposts(state: state, target: test_note.id))
}
}

View File

@@ -0,0 +1,66 @@
//
// RepostAction.swift
// damus
//
// Created by William Casarin on 2023-04-19.
//
import SwiftUI
struct RepostAction: View {
let damus_state: DamusState
let event: NostrEvent
@Environment(\.dismiss) var dismiss
@Environment(\.colorScheme) var colorScheme
var body: some View {
VStack(alignment: .leading) {
Button {
dismiss()
guard let keypair = self.damus_state.keypair.to_full(),
let boost = make_boost_event(keypair: keypair, boosted: self.event, relayURL: damus_state.nostrNetwork.relaysForEvent(event: self.event).first) else {
return
}
damus_state.nostrNetwork.postbox.send(boost)
} label: {
Label(NSLocalizedString("Repost", comment: "Button to repost a note"), image: "repost")
.frame(maxWidth: .infinity, minHeight: 50, maxHeight: 50, alignment: .leading)
}
.font(.system(size: 20, weight: .regular))
.foregroundColor(colorScheme == .light ? Color("DamusBlack") : Color("DamusWhite"))
.padding(EdgeInsets(top: 25, leading: 50, bottom: 0, trailing: 50))
Button {
dismiss()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
notify(.compose(.quoting(self.event)))
}
} label: {
Label(NSLocalizedString("Quote", comment: "Button to compose a quoted note"), systemImage: "quote.opening")
.frame(maxWidth: .infinity, minHeight: 50, maxHeight: 50, alignment: .leading)
}
.font(.system(size: 20, weight: .regular))
.foregroundColor(colorScheme == .light ? Color("DamusBlack") : Color("DamusWhite"))
.padding(EdgeInsets(top: 0, leading: 50, bottom: 0, trailing: 50))
HStack {
BigButton(NSLocalizedString("Cancel", comment: "Button to cancel a repost.")) {
dismiss()
}
}
}
}
}
struct RepostAction_Previews: PreviewProvider {
static var previews: some View {
RepostAction(damus_state: test_damus_state, event: test_note)
}
}

View File

@@ -0,0 +1,24 @@
//
// RepostView.swift
// damus
//
// Created by Terry Yiu on 1/22/23.
//
import SwiftUI
struct RepostView: View {
let damus_state: DamusState
let repost: NostrEvent
var body: some View {
FollowUserView(target: .pubkey(repost.pubkey), damus_state: damus_state)
}
}
struct RepostView_Previews: PreviewProvider {
static var previews: some View {
RepostView(damus_state: test_damus_state, repost: NostrEvent(content: "", keypair: test_keypair)!)
}
}

View File

@@ -0,0 +1,77 @@
//
// Reposted.swift
// damus
//
// Created by William Casarin on 2023-01-11.
//
import SwiftUI
struct Reposted: View {
let damus: DamusState
let pubkey: Pubkey
let target: NostrEvent
@State var reposts: Int
init(damus: DamusState, pubkey: Pubkey, target: NostrEvent) {
self.damus = damus
self.pubkey = pubkey
self.target = target
self.reposts = damus.boosts.counts[target.id] ?? 1
}
var body: some View {
HStack(alignment: .center) {
Image("repost")
.foregroundColor(Color.gray)
// Show profile picture of the reposter only if the reposter is not the author of the reposted note.
if pubkey != target.pubkey {
ProfilePicView(pubkey: pubkey, size: eventview_pfp_size(.small), highlight: .none, profiles: damus.profiles, disable_animation: damus.settings.disable_animation)
.onTapGesture {
show_profile_action_sheet_if_enabled(damus_state: damus, pubkey: pubkey)
}
.onLongPressGesture(minimumDuration: 0.1) {
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
damus.nav.push(route: Route.ProfileByKey(pubkey: pubkey))
}
}
NavigationLink(value: Route.Reposts(reposts: .reposts(state: damus, target: target.id))) {
Text(people_reposted_text(profiles: damus.profiles, pubkey: pubkey, reposts: reposts))
.font(.subheadline)
.foregroundColor(.gray)
}
}
.onReceive(handle_notify(.update_stats), perform: { note_id in
guard note_id == target.id else { return }
let repost_count = damus.boosts.counts[target.id]
if let repost_count, reposts != repost_count {
reposts = repost_count
}
})
}
}
func people_reposted_text(profiles: Profiles, pubkey: Pubkey, reposts: Int, locale: Locale = Locale.current) -> String {
guard reposts > 0 else {
return ""
}
let bundle = bundleForLocale(locale: locale)
let other_reposts = reposts - 1
let display_name = event_author_name(profiles: profiles, pubkey: pubkey)
if other_reposts == 0 {
return String(format: NSLocalizedString("%@ reposted", bundle: bundle, comment: "Text indicating that the note was reposted (i.e. re-shared)."), locale: locale, display_name)
} else {
return String(format: localizedStringFormat(key: "people_reposted_count", locale: locale), locale: locale, other_reposts, display_name)
}
}
struct Reposted_Previews: PreviewProvider {
static var previews: some View {
let test_state = test_damus_state
Reposted(damus: test_state, pubkey: test_state.pubkey, target: test_note)
}
}

View File

@@ -0,0 +1,44 @@
//
// RepostedEvent.swift
// damus
//
// Created by William Casarin on 2023-03-23.
//
import SwiftUI
struct RepostedEvent: View {
let damus: DamusState
let event: NostrEvent
let inner_ev: NostrEvent
let options: EventViewOptions
var body: some View {
VStack(alignment: .leading) {
NavigationLink(value: Route.ProfileByKey(pubkey: event.pubkey)) {
Reposted(damus: damus, pubkey: event.pubkey, target: inner_ev)
.padding(.horizontal)
}
.buttonStyle(PlainButtonStyle())
//SelectedEventView(damus: damus, event: inner_ev, size: .normal)
EventMutingContainerView(
damus_state: damus,
event: inner_ev,
muteBox: { event_shown, muted_reason in
AnyView(
EventMutedBoxView(shown: event_shown, reason: muted_reason)
.padding(.horizontal, 5) // Add a bit of horizontal padding to avoid the mute box from touching the edges of the screen
)
}) {
EventView(damus: damus, event: inner_ev, pubkey: inner_ev.pubkey, options: options.union(.wide))
}
}
}
}
struct RepostedEvent_Previews: PreviewProvider {
static var previews: some View {
RepostedEvent(damus: test_damus_state, event: test_note, inner_ev: test_note, options: [])
}
}

View File

@@ -0,0 +1,39 @@
//
// RepostsView.swift
// damus
//
// Created by Terry Yiu on 1/22/23.
//
import SwiftUI
struct RepostsView: View {
let damus_state: DamusState
@StateObject var model: EventsModel
var body: some View {
ScrollView {
LazyVStack {
ForEach(model.events.events, id: \.id) { ev in
RepostView(damus_state: damus_state, repost: ev)
}
}
.padding()
}
.padding(.bottom, tabHeight)
.navigationBarTitle(NSLocalizedString("Reposts", comment: "Navigation bar title for Reposts view."))
.onAppear {
model.subscribe()
}
.onDisappear {
model.unsubscribe()
}
}
}
struct RepostsView_Previews: PreviewProvider {
static var previews: some View {
let state = test_damus_state
RepostsView(damus_state: state, model: .reposts(state: state, target: test_note.id))
}
}