Switch to NavigationStack

Changelog-Changed: Drop iOS15 support
Changelog-Fixed: Fixed navigation popping issues
This commit is contained in:
Scott Penrose
2023-06-30 06:43:22 -07:00
committed by William Casarin
42 changed files with 548 additions and 315 deletions
+8
View File
@@ -304,6 +304,7 @@
9CA876E229A00CEA0003B9A3 /* AttachMediaUtility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CA876E129A00CE90003B9A3 /* AttachMediaUtility.swift */; };
BA693074295D649800ADDB87 /* UserSettingsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA693073295D649800ADDB87 /* UserSettingsStore.swift */; };
BAB68BED29543FA3007BA466 /* SelectWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */; };
D2277EEA2A089BD5006C3807 /* Router.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2277EE92A089BD5006C3807 /* Router.swift */; };
DD597CBD2963D85A00C64D32 /* MarkdownTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD597CBC2963D85A00C64D32 /* MarkdownTests.swift */; };
E4FA1C032A24BB7F00482697 /* SearchSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4FA1C022A24BB7F00482697 /* SearchSettingsView.swift */; };
E990020F2955F837003BBC5A /* EditMetadataView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E990020E2955F837003BBC5A /* EditMetadataView.swift */; };
@@ -753,6 +754,7 @@
9CA876E129A00CE90003B9A3 /* AttachMediaUtility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachMediaUtility.swift; sourceTree = "<group>"; };
BA693073295D649800ADDB87 /* UserSettingsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettingsStore.swift; sourceTree = "<group>"; };
BAB68BEC29543FA3007BA466 /* SelectWalletView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectWalletView.swift; sourceTree = "<group>"; };
D2277EE92A089BD5006C3807 /* Router.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Router.swift; sourceTree = "<group>"; };
DD597CBC2963D85A00C64D32 /* MarkdownTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarkdownTests.swift; sourceTree = "<group>"; };
E4FA1C022A24BB7F00482697 /* SearchSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchSettingsView.swift; sourceTree = "<group>"; };
E990020E2955F837003BBC5A /* EditMetadataView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditMetadataView.swift; sourceTree = "<group>"; };
@@ -1155,6 +1157,7 @@
50B5685229F97CB400A23243 /* CredentialHandler.swift */,
4C7D09582A05BEAD00943473 /* KeyboardVisible.swift */,
3A8CC6CB2A2CFEF900940F5F /* StringUtil.swift */,
D2277EE92A089BD5006C3807 /* Router.swift */,
);
path = Util;
sourceTree = "<group>";
@@ -1307,7 +1310,9 @@
4CE6DEE427F7A08100C66700 /* Products */,
4CEE2AE62804F57B00AB5EEF /* Frameworks */,
);
indentWidth = 4;
sourceTree = "<group>";
tabWidth = 4;
};
4CE6DEE427F7A08100C66700 /* Products */ = {
isa = PBXGroup;
@@ -1861,6 +1866,7 @@
5C6E1DAD2A193EC2008FC15A /* GradientButtonStyle.swift in Sources */,
4C8EC52529D1FA6C0085D9A8 /* DamusColors.swift in Sources */,
3A4647CF2A413ADC00386AD8 /* CondensedProfilePicturesView.swift in Sources */,
D2277EEA2A089BD5006C3807 /* Router.swift in Sources */,
4C5C7E6A284EDE2E00A22DF5 /* SearchResultsView.swift in Sources */,
4CE1399429F0669900AC6A0B /* BigButton.swift in Sources */,
7C60CAEF298471A1009C80D6 /* CoreSVG.swift in Sources */,
@@ -2254,6 +2260,7 @@
INFOPLIST_KEY_UILaunchStoryboardName = Launch.storyboard;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -2302,6 +2309,7 @@
INFOPLIST_KEY_UILaunchStoryboardName = Launch.storyboard;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
+1 -12
View File
@@ -11,21 +11,10 @@ struct UserViewRow: View {
let damus_state: DamusState
let pubkey: String
@State var navigating: Bool = false
var body: some View {
let dest = ProfileView(damus_state: damus_state, pubkey: pubkey)
UserView(damus_state: damus_state, pubkey: pubkey)
.contentShape(Rectangle())
.background(
NavigationLink(destination: dest, isActive: $navigating) {
EmptyView()
}
)
.onTapGesture {
navigating = true
}
.background(.clear)
}
}
+25 -62
View File
@@ -82,14 +82,6 @@ struct ContentView: View {
@State var damus_state: DamusState? = nil
@SceneStorage("ContentView.selected_timeline") var selected_timeline: Timeline = .home
@State var is_deleted_account: Bool = false
@State var active_profile: String? = nil
@State var active_search: NostrFilter? = nil
@State var active_event: NostrEvent? = nil
@State var profile_open: Bool = false
@State var thread_open: Bool = false
@State var search_open: Bool = false
@State var wallet_open: Bool = false
@State var active_nwc: WalletConnectURL? = nil
@State var muting: String? = nil
@State var confirm_mute: Bool = false
@State var user_muted_confirm: Bool = false
@@ -97,6 +89,7 @@ struct ContentView: View {
@SceneStorage("ContentView.filter_state") var filter_state : FilterState = .posts_and_replies
@State private var isSideBarOpened = false
var home: HomeModel = HomeModel()
@StateObject var navigationCoordinator: NavigationCoordinator = NavigationCoordinator()
let sub_id = UUID().description
@@ -150,15 +143,13 @@ struct ContentView: View {
ZStack {
if let damus = self.damus_state {
TimelineView(events: home.events, loading: .constant(false), damus: damus, show_friend_icon: false, filter: filter)
.environmentObject(navigationCoordinator)
}
}
}
func popToRoot() {
profile_open = false
thread_open = false
search_open = false
wallet_open = false
navigationCoordinator.popToRoot()
isSideBarOpened = false
}
@@ -169,29 +160,16 @@ struct ContentView: View {
func MainContent(damus: DamusState) -> some View {
VStack {
NavigationLink(destination: WalletView(damus_state: damus, model: damus_state!.wallet), isActive: $wallet_open) {
EmptyView()
}
NavigationLink(destination: MaybeProfileView, isActive: $profile_open) {
EmptyView()
}
if let active_event {
let thread = ThreadModel(event: active_event, damus_state: damus_state!)
NavigationLink(destination: ThreadView(state: damus_state!, thread: thread), isActive: $thread_open) {
EmptyView()
}
}
NavigationLink(destination: MaybeSearchView, isActive: $search_open) {
EmptyView()
}
switch selected_timeline {
case .search:
if #available(iOS 16.0, *) {
SearchHomeView(damus_state: damus_state!, model: SearchHomeModel(damus_state: damus_state!))
.environmentObject(navigationCoordinator)
.scrollDismissesKeyboard(.immediately)
} else {
// Fallback on earlier versions
SearchHomeView(damus_state: damus_state!, model: SearchHomeModel(damus_state: damus_state!))
.environmentObject(navigationCoordinator)
}
case .home:
@@ -199,9 +177,11 @@ struct ContentView: View {
case .notifications:
NotificationsView(state: damus, notifications: home.notifications)
.environmentObject(navigationCoordinator)
case .dms:
DirectMessagesView(damus_state: damus_state!, model: damus_state!.dms, settings: damus_state!.settings)
.environmentObject(navigationCoordinator)
}
}
.navigationBarTitle(timeline_name(selected_timeline), displayMode: .inline)
@@ -225,28 +205,6 @@ struct ContentView: View {
}
}
var MaybeSearchView: some View {
Group {
if let search = self.active_search {
SearchView(appstate: damus_state!, search: SearchModel(state: damus_state!, search: search))
} else {
EmptyView()
}
}
}
var MaybeProfileView: some View {
Group {
if let pk = self.active_profile {
let profile_model = ProfileModel(pubkey: pk, damus: damus_state!)
let followers = FollowersModel(damus_state: damus_state!, target: pk)
ProfileView(damus_state: damus_state!, profile: profile_model, followers: followers)
} else {
EmptyView()
}
}
}
func MaybeReportView(target: ReportTarget) -> some View {
Group {
if let damus_state {
@@ -262,32 +220,30 @@ struct ContentView: View {
}
func open_event(ev: NostrEvent) {
popToRoot()
self.active_event = ev
self.thread_open = true
let thread = ThreadModel(event: ev, damus_state: damus_state!)
navigationCoordinator.push(route: Route.Thread(thread: thread))
}
func open_wallet(nwc: WalletConnectURL) {
self.damus_state!.wallet.new(nwc)
self.wallet_open = true
navigationCoordinator.push(route: Route.Wallet(wallet: damus_state!.wallet))
}
func open_profile(id: String) {
popToRoot()
self.active_profile = id
self.profile_open = true
let profile_model = ProfileModel(pubkey: id, damus: damus_state!)
let followers = FollowersModel(damus_state: damus_state!, target: id)
navigationCoordinator.push(route: Route.Profile(profile: profile_model, followers: followers))
}
func open_search(filt: NostrFilter) {
popToRoot()
self.active_search = filt
self.search_open = true
let search = SearchModel(state: damus_state!, search: filt)
navigationCoordinator.push(route: Route.Search(search: search))
}
var body: some View {
VStack(alignment: .leading, spacing: 0) {
if let damus = self.damus_state {
NavigationView {
NavigationStack(path: $navigationCoordinator.path) {
TabView { // Prevents navbar appearance change on scroll
MainContent(damus: damus)
.toolbar() {
@@ -326,7 +282,14 @@ struct ContentView: View {
.tabViewStyle(.page(indexDisplayMode: .never))
.overlay(
SideMenuView(damus_state: damus, isSidebarVisible: $isSideBarOpened.animation())
.environmentObject(navigationCoordinator)
)
.navigationDestination(for: Route.self) { route in
route.view(navigationCordinator: navigationCoordinator, damusState: damus_state!)
}
.onReceive(handle_notify(.switched_timeline)) { _ in
navigationCoordinator.popToRoot()
}
}
.navigationViewStyle(.stack)
@@ -520,8 +483,8 @@ struct ContentView: View {
switch local.type {
case .dm:
selected_timeline = .dms
damus_state.dms.open_dm_by_pk(target.pubkey)
damus_state.dms.set_active_dm(target.pubkey)
navigationCoordinator.push(route: Route.DMChat(dms: damus_state.dms.active_model))
case .like: fallthrough
case .zap: fallthrough
case .mention: fallthrough
-10
View File
@@ -30,16 +30,6 @@ class DirectMessagesModel: ObservableObject {
self.active_model = model
}
func open_dm_by_pk(_ pubkey: String) {
self.set_active_dm(pubkey)
self.open_dm = true
}
func open_dm_by_model(_ model: DirectMessageModel) {
self.set_active_dm_model(model)
self.open_dm = true
}
func set_active_dm(_ pubkey: String) {
for model in self.dms where model.pubkey == pubkey {
self.set_active_dm_model(model)
+288
View File
@@ -0,0 +1,288 @@
//
// Router.swift
// damus
//
// Created by Scott Penrose on 5/7/23.
//
import SwiftUI
enum Route: Hashable {
case ProfileByKey(pubkey: String)
case Profile(profile: ProfileModel, followers: FollowersModel)
case Followers(environmentObject: FollowersModel)
case Relay(relay: String, showActionButtons: Binding<Bool>)
case RelayDetail(relay: String, metadata: RelayMetadata)
case Following(following: FollowingModel)
case MuteList(users: [String])
case RelayConfig
case Bookmarks
case Config
case EditMetadata
case DMChat(dms: DirectMessageModel)
case UserRelays(relays: [String])
case KeySettings(keypair: Keypair)
case AppearanceSettings(settings: UserSettingsStore)
case NotificationSettings(settings: UserSettingsStore)
case ZapSettings(settings: UserSettingsStore)
case TranslationSettings(settings: UserSettingsStore)
case SearchSettings(settings: UserSettingsStore)
case Thread(thread: ThreadModel)
case Reposts(reposts: RepostsModel)
case Reactions(reactions: ReactionsModel)
case Zaps(target: ZapTarget)
case Search(search: SearchModel)
case EULA
case Login
case CreateAccount
case SaveKeys(account: CreateAccountModel)
case Wallet(wallet: WalletModel)
case WalletScanner(result: Binding<WalletScanResult>)
case FollowersYouKnow(friendedFollowers: [String])
@ViewBuilder
func view(navigationCordinator: NavigationCoordinator, damusState: DamusState) -> some View {
switch self {
case .ProfileByKey(let pubkey):
ProfileView(damus_state: damusState, pubkey: pubkey)
.environmentObject(navigationCordinator)
case .Profile(let profile, let followers):
ProfileView(damus_state: damusState, profile: profile, followers: followers)
.environmentObject(navigationCordinator)
case .Followers(let environmentObject):
FollowersView(damus_state: damusState)
.environmentObject(environmentObject)
case .Relay(let relay, let showActionButtons):
RelayView(state: damusState, relay: relay, showActionButtons: showActionButtons)
case .RelayDetail(let relay, let metadata):
RelayDetailView(state: damusState, relay: relay, nip11: metadata)
case .Following(let following):
FollowingView(damus_state: damusState, following: following)
.environmentObject(navigationCordinator)
case .MuteList(let users):
MutelistView(damus_state: damusState, users: users)
case .RelayConfig:
RelayConfigView(state: damusState)
case .Bookmarks:
BookmarksView(state: damusState)
.environmentObject(navigationCordinator)
case .Config:
ConfigView(state: damusState)
case .EditMetadata:
EditMetadataView(damus_state: damusState)
case .DMChat(let dms):
DMChatView(damus_state: damusState, dms: dms)
case .UserRelays(let relays):
UserRelaysView(state: damusState, relays: relays)
case .KeySettings(let keypair):
KeySettingsView(keypair: keypair)
case .AppearanceSettings(let settings):
AppearanceSettingsView(settings: settings)
case .NotificationSettings(let settings):
NotificationSettingsView(settings: settings)
case .ZapSettings(let settings):
ZapSettingsView(settings: settings)
case .TranslationSettings(let settings):
NotificationSettingsView(settings: settings)
case .SearchSettings(let settings):
SearchSettingsView(settings: settings)
case .Thread(let thread):
ThreadView(state: damusState, thread: thread)
case .Reposts(let reposts):
RepostsView(damus_state: damusState, model: reposts)
.environmentObject(navigationCordinator)
case .Reactions(let reactions):
ReactionsView(damus_state: damusState, model: reactions)
.environmentObject(navigationCordinator)
case .Zaps(let target):
ZapsView(state: damusState, target: target)
case .Search(let search):
SearchView(appstate: damusState, search: search)
.environmentObject(navigationCordinator)
case .EULA:
EULAView()
.environmentObject(navigationCordinator)
case .Login:
LoginView()
.environmentObject(navigationCordinator)
case .CreateAccount:
CreateAccountView()
.environmentObject(navigationCordinator)
case .SaveKeys(let account):
SaveKeysView(account: account)
.environmentObject(navigationCordinator)
case .Wallet(let walletModel):
WalletView(damus_state: damusState, model: walletModel)
.environmentObject(navigationCordinator)
case .WalletScanner(let walletScanResult):
WalletScannerView(result: walletScanResult)
case .FollowersYouKnow(let friendedFollowers):
FollowersYouKnowView(damus_state: damusState, friended_followers: friendedFollowers)
}
}
static func == (lhs: Route, rhs: Route) -> Bool {
switch (lhs, rhs) {
case (.ProfileByKey (let lhs_pubkey), .ProfileByKey(let rhs_pubkey)):
return lhs_pubkey == rhs_pubkey
case (.Profile (let lhs_profile, _), .Profile(let rhs_profile, _)):
return lhs_profile == rhs_profile
case (.Followers (_), .Followers (_)):
return true
case (.Relay (let lhs_relay, _), .Relay (let rhs_relay, _)):
return lhs_relay == rhs_relay
case (.RelayDetail(let lhs_relay, _), .RelayDetail(let rhs_relay, _)):
return lhs_relay == rhs_relay
case (.Following(_), .Following(_)):
return true
case (.MuteList(let lhs_users), .MuteList(let rhs_users)):
return lhs_users == rhs_users
case (.RelayConfig, .RelayConfig):
return true
case (.Bookmarks, .Bookmarks):
return true
case (.Config, .Config):
return true
case (.EditMetadata, .EditMetadata):
return true
case (.DMChat(let lhs_dms), .DMChat(let rhs_dms)):
return lhs_dms.our_pubkey == rhs_dms.our_pubkey
case (.UserRelays(let lhs_relays), .UserRelays(let rhs_relays)):
return lhs_relays == rhs_relays
case (.KeySettings(let lhs_keypair), .KeySettings(let rhs_keypair)):
return lhs_keypair.pubkey == rhs_keypair.pubkey
case (.AppearanceSettings(_), .AppearanceSettings(_)):
return true
case (.NotificationSettings(_), .NotificationSettings(_)):
return true
case (.ZapSettings(_), .ZapSettings(_)):
return true
case (.TranslationSettings(_), .TranslationSettings(_)):
return true
case (.SearchSettings(_), .SearchSettings(_)):
return true
case (.Thread(let lhs_threadModel), .Thread(thread: let rhs_threadModel)):
return lhs_threadModel.event.id == rhs_threadModel.event.id
case (.Reposts(let lhs_reposts), .Reposts(let rhs_reposts)):
return lhs_reposts.target == rhs_reposts.target
case (.Reactions(let lhs_reactions), .Reactions(let rhs_reactions)):
return lhs_reactions.target == rhs_reactions.target
case (.Zaps(let lhs_target), .Zaps(let rhs_target)):
return lhs_target == rhs_target
case (.Search(let lhs_search), .Search(let rhs_search)):
return lhs_search.sub_id == rhs_search.sub_id && lhs_search.profiles_subid == rhs_search.profiles_subid
case (.EULA, .EULA):
return true
case (.Login, .Login):
return true
case (.CreateAccount, .CreateAccount):
return true
case (.SaveKeys(let lhs_account), .SaveKeys(let rhs_account)):
return lhs_account.pubkey == rhs_account.pubkey
case (.Wallet(_), .Wallet(_)):
return true
case (.WalletScanner(_), .WalletScanner(_)):
return true
case (.FollowersYouKnow(let lhs_friendedFollowers), .FollowersYouKnow(let rhs_friendedFollowers)):
return lhs_friendedFollowers == rhs_friendedFollowers
default:
return false
}
}
func hash(into hasher: inout Hasher) {
switch self {
case .ProfileByKey(let pubkey):
hasher.combine("profilebykey")
hasher.combine(pubkey)
case .Profile(let profile, _):
hasher.combine("profile")
hasher.combine(profile.pubkey)
case .Followers(_):
hasher.combine("followers")
case .Relay(let relay, _):
hasher.combine("relay")
hasher.combine(relay)
case .RelayDetail(let relay, _):
hasher.combine("relayDetail")
hasher.combine(relay)
case .Following(_):
hasher.combine("following")
case .MuteList(let users):
hasher.combine("muteList")
hasher.combine(users)
case .RelayConfig:
hasher.combine("relayConfig")
case .Bookmarks:
hasher.combine("bookmarks")
case .Config:
hasher.combine("config")
case .EditMetadata:
hasher.combine("editMetadata")
case .DMChat(let dms):
hasher.combine("dms")
hasher.combine(dms.our_pubkey)
case .UserRelays(let relays):
hasher.combine("userRelays")
hasher.combine(relays)
case .KeySettings(let keypair):
hasher.combine("keySettings")
hasher.combine(keypair.pubkey)
case .AppearanceSettings(_):
hasher.combine("appearanceSettings")
case .NotificationSettings(_):
hasher.combine("notificationSettings")
case .ZapSettings(_):
hasher.combine("zapSettings")
case .TranslationSettings(_):
hasher.combine("translationSettings")
case .SearchSettings(_):
hasher.combine("searchSettings")
case .Thread(let threadModel):
hasher.combine("thread")
hasher.combine(threadModel.event.id)
case .Reposts(let reposts):
hasher.combine("reposts")
hasher.combine(reposts.target)
case .Zaps(let target):
hasher.combine("zaps")
hasher.combine(target.id)
hasher.combine(target.pubkey)
case .Reactions(let reactions):
hasher.combine("reactions")
hasher.combine(reactions.target)
case .Search(let search):
hasher.combine("search")
hasher.combine(search.sub_id)
hasher.combine(search.profiles_subid)
case .EULA:
hasher.combine("eula")
case .Login:
hasher.combine("login")
case .CreateAccount:
hasher.combine("createAccount")
case .SaveKeys(let account):
hasher.combine("saveKeys")
hasher.combine(account.pubkey)
case .Wallet(_):
hasher.combine("wallet")
case .WalletScanner(_):
hasher.combine("walletScanner")
case .FollowersYouKnow(let friendedFollowers):
hasher.combine("followersYouKnow")
hasher.combine(friendedFollowers)
}
}
}
class NavigationCoordinator: ObservableObject {
@Published var path = [Route]()
func push(route: Route) {
path.append(route)
}
func popToRoot() {
path = []
}
}
+3 -4
View File
@@ -25,7 +25,7 @@ struct EventDetailBar: View {
var body: some View {
HStack {
if bar.boosts > 0 {
NavigationLink(destination: RepostsView(damus_state: state, model: RepostsModel(state: state, target: target))) {
NavigationLink(value: Route.Reposts(reposts: RepostsModel(state: state, target: target))) {
let noun = Text(verbatim: repostsCountString(bar.boosts)).foregroundColor(.gray)
Text("\(Text(verbatim: bar.boosts.formatted()).font(.body.bold())) \(noun)", comment: "Sentence composed of 2 variables to describe how many reposts. In source English, the first variable is the number of reposts, and the second variable is 'Repost' or 'Reposts'.")
}
@@ -33,7 +33,7 @@ struct EventDetailBar: View {
}
if bar.likes > 0 && !state.settings.onlyzaps_mode {
NavigationLink(destination: ReactionsView(damus_state: state, model: ReactionsModel(state: state, target: target))) {
NavigationLink(value: Route.Reactions(reactions: ReactionsModel(state: state, target: target))) {
let noun = Text(verbatim: reactionsCountString(bar.likes)).foregroundColor(.gray)
Text("\(Text(verbatim: bar.likes.formatted()).font(.body.bold())) \(noun)", comment: "Sentence composed of 2 variables to describe how many reactions there are on a post. In source English, the first variable is the number of reactions, and the second variable is 'Reaction' or 'Reactions'.")
}
@@ -41,8 +41,7 @@ struct EventDetailBar: View {
}
if bar.zaps > 0 {
let dst = ZapsView(state: state, target: .note(id: target, author: target_pk))
NavigationLink(destination: dst) {
NavigationLink(value: Route.Zaps(target: .note(id: target, author: target_pk))) {
let noun = Text(verbatim: zapsCountString(bar.zaps)).foregroundColor(.gray)
Text("\(Text(verbatim: bar.zaps.formatted()).font(.body.bold())) \(noun)", comment: "Sentence composed of 2 variables to describe how many zap payments there are on a post. In source English, the first variable is the number of zap payments, and the second variable is 'Zap' or 'Zaps'.")
}
+2
View File
@@ -14,6 +14,7 @@ struct BookmarksView: View {
@State private var clearAllAlert: Bool = false
@Environment(\.dismiss) var dismiss
@EnvironmentObject var navigationCoordinator: NavigationCoordinator
@ObservedObject var manager: BookmarksManager
init(state: DamusState) {
@@ -38,6 +39,7 @@ struct BookmarksView: View {
} else {
ScrollView {
InnerTimelineView(events: EventHolder(events: bookmarks, incoming: []), damus: state, filter: noneFilter)
.environmentObject(navigationCoordinator)
}
}
+17 -18
View File
@@ -18,16 +18,16 @@ struct ConfigView: View {
@State var delete_account_warning: Bool = false
@State var confirm_delete_account: Bool = false
@State var delete_text: String = ""
@ObservedObject var settings: UserSettingsStore
private let DELETE_KEYWORD = "DELETE"
init(state: DamusState) {
self.state = state
_settings = ObservedObject(initialValue: state.settings)
}
func textColor() -> Color {
colorScheme == .light ? DamusColors.black : DamusColors.white
}
@@ -36,31 +36,30 @@ struct ConfigView: View {
ZStack(alignment: .leading) {
Form {
Section {
NavigationLink(destination: KeySettingsView(keypair: state.keypair)) {
NavigationLink(value: Route.KeySettings(keypair: state.keypair)) {
IconLabel(NSLocalizedString("Keys", comment: "Settings section for managing keys"), img_name: "key", color: .purple)
}
NavigationLink(destination: AppearanceSettingsView(settings: settings)) {
NavigationLink(value: Route.AppearanceSettings(settings: settings)) {
IconLabel(NSLocalizedString("Appearance", comment: "Section header for text and appearance settings"), img_name: "eye", color: .red)
}
NavigationLink(destination: SearchSettingsView(settings: settings)) {
NavigationLink(value: Route.SearchSettings(settings: settings)) {
IconLabel(NSLocalizedString("Search/Universe", comment: "Section header for search/universe settings"), img_name: "magnifyingglass", color: .red)
}
NavigationLink(destination: NotificationSettingsView(settings: settings)) {
NavigationLink(value: Route.NotificationSettings(settings: settings)) {
IconLabel(NSLocalizedString("Notifications", comment: "Section header for Damus notifications"), img_name: "notification-bell-on", color: .blue)
}
NavigationLink(destination: ZapSettingsView(settings: settings)) {
NavigationLink(value: Route.ZapSettings(settings: settings)) {
IconLabel(NSLocalizedString("Zaps", comment: "Section header for zap settings"), img_name: "zap.fill", color: .orange)
}
NavigationLink(destination: TranslationSettingsView(settings: settings)) {
NavigationLink(value: Route.TranslationSettings(settings: settings)) {
IconLabel(NSLocalizedString("Translation", comment: "Section header for text and appearance settings"), img_name: "globe", color: .green)
}
}
Section(NSLocalizedString("Sign Out", comment: "Section title for signing out")) {
Button(action: {
@@ -116,11 +115,11 @@ struct ConfigView: View {
guard let full_kp = state.keypair.to_full() else {
return
}
guard delete_text == DELETE_KEYWORD else {
return
}
let ev = created_deleted_account_profile(keypair: full_kp)
state.postbox.send(ev)
notify(.logout, ())
@@ -164,7 +163,7 @@ func handle_string_amount(new_value: String) -> Int? {
guard let amt = NumberFormatter().number(from: filtered) as? Int else {
return nil
}
return amt
}
+2 -7
View File
@@ -10,8 +10,7 @@ import SwiftUI
struct CreateAccountView: View {
@StateObject var account: CreateAccountModel = CreateAccountModel()
@StateObject var profileUploadViewModel = ProfileUploadingViewModel()
@State var is_done: Bool = false
@EnvironmentObject var navigationCoordinator: NavigationCoordinator
func SignupForm<FormContent: View>(@ViewBuilder content: () -> FormContent) -> some View {
return VStack(alignment: .leading, spacing: 10.0, content: content)
@@ -25,10 +24,6 @@ struct CreateAccountView: View {
var body: some View {
ZStack(alignment: .top) {
NavigationLink(destination: SaveKeysView(account: account), isActive: $is_done) {
EmptyView()
}
VStack {
VStack(alignment: .center) {
ProfilePictureSelector(pubkey: account.pubkey, viewModel: profileUploadViewModel, callback: uploadedProfilePicture(image_url:))
@@ -63,7 +58,7 @@ struct CreateAccountView: View {
.padding(.top, 10)
Button(action: {
self.is_done = true
navigationCoordinator.push(route: Route.SaveKeys(account: account))
}) {
HStack {
Text("Create account now", comment: "Button to create account.")
+1 -2
View File
@@ -61,8 +61,7 @@ struct DMChatView: View, KeyboardReadable {
var Header: some View {
let profile = damus_state.profiles.lookup(id: pubkey)
let profile_page = ProfileView(damus_state: damus_state, pubkey: pubkey)
return NavigationLink(destination: profile_page) {
return NavigationLink(value: Route.ProfileByKey(pubkey: pubkey)) {
HStack {
ProfilePicView(pubkey: pubkey, size: 24, highlight: .none, profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation)
+4 -5
View File
@@ -18,13 +18,11 @@ struct DirectMessagesView: View {
@State var dm_type: DMType = .friend
@ObservedObject var model: DirectMessagesModel
@ObservedObject var settings: UserSettingsStore
@EnvironmentObject var navigationCoordinator: NavigationCoordinator
func MainContent(requests: Bool) -> some View {
ScrollView {
let chat = DMChatView(damus_state: damus_state, dms: model.active_model)
NavigationLink(destination: chat, isActive: $model.open_dm) {
EmptyView()
}
LazyVStack(spacing: 0) {
if model.dms.isEmpty, !model.loading {
EmptyTimelineView()
@@ -54,7 +52,8 @@ struct DirectMessagesView: View {
if ok, let ev = model.events.last {
EventView(damus: damus_state, event: ev, pubkey: model.pubkey, options: options)
.onTapGesture {
self.model.open_dm_by_model(model)
self.model.set_active_dm_model(model)
navigationCoordinator.push(route: Route.DMChat(dms: self.model.active_model))
}
Divider()
+2 -8
View File
@@ -56,18 +56,13 @@ By using our Application, you signify your acceptance of this EULA. If you do no
"""
struct EULAView: View {
@State private var login = false
@State var accepted = false
@Environment(\.colorScheme) var colorScheme
@Environment(\.dismiss) var dismiss
@EnvironmentObject var navigationCoordinator: NavigationCoordinator
var body: some View {
ZStack {
ScrollView {
NavigationLink(destination: LoginView(accepted: $accepted), isActive: $login) {
EmptyView()
}
Text(Markdown.parse(content: eula))
.padding()
}
@@ -96,8 +91,7 @@ struct EULAView: View {
}
Button(action: {
accepted = true
login.toggle()
navigationCoordinator.push(route: Route.Login)
}) {
HStack {
Text("Accept", comment: "Button to accept the end user license agreement before being allowed into the app.")
+1 -2
View File
@@ -72,8 +72,7 @@ struct BuilderEventView: View {
if let event {
let ev = event.get_inner_event(cache: damus.events) ?? event
let thread = ThreadModel(event: ev, damus_state: damus)
let dest = ThreadView(state: damus, thread: thread)
NavigationLink(destination: dest) {
NavigationLink(value: Route.Thread(thread: thread)) {
EventView(damus: damus, event: event, options: .embedded)
.padding([.top, .bottom], 8)
}.buttonStyle(.plain)
+1 -1
View File
@@ -37,7 +37,7 @@ struct EventProfile: View {
var body: some View {
HStack(alignment: .center) {
VStack {
NavigationLink(destination: ProfileView(damus_state: damus_state, pubkey: pubkey)) {
NavigationLink(value: Route.ProfileByKey(pubkey: pubkey)) {
ProfilePicView(pubkey: pubkey, size: pfp_size, highlight: .none, profiles: damus_state.profiles, disable_animation: disable_animation)
}
}
+9 -6
View File
@@ -10,18 +10,16 @@ import SwiftUI
struct FollowUserView: View {
let target: FollowTarget
let damus_state: DamusState
@EnvironmentObject var navigationCoordinator: NavigationCoordinator
static let markdown = Markdown()
@State var navigating: Bool = false
var body: some View {
let dest = ProfileView(damus_state: damus_state, pubkey: target.pubkey)
NavigationLink(destination: dest, isActive: $navigating) {
EmptyView()
}
HStack {
UserViewRow(damus_state: damus_state, pubkey: target.pubkey)
.onTapGesture {
navigationCoordinator.push(route: Route.ProfileByKey(pubkey: target.pubkey))
}
FollowButtonView(target: target, follows_you: false, follow_state: damus_state.contacts.follow_state(target.pubkey))
}
@@ -52,12 +50,14 @@ struct FollowersView: View {
let damus_state: DamusState
@EnvironmentObject var followers: FollowersModel
@EnvironmentObject var navigationCoordinator: NavigationCoordinator
var body: some View {
ScrollView {
LazyVStack(alignment: .leading) {
ForEach(followers.contacts ?? [], id: \.self) { pk in
FollowUserView(target: .pubkey(pk), damus_state: damus_state)
.environmentObject(navigationCoordinator)
}
}
.padding(.horizontal)
@@ -76,12 +76,15 @@ struct FollowingView: View {
let damus_state: DamusState
let following: FollowingModel
@EnvironmentObject var navigationCoordinator: NavigationCoordinator
var body: some View {
ScrollView {
LazyVStack(alignment: .leading) {
ForEach(following.contacts.reversed(), id: \.self) { pk in
FollowUserView(target: .pubkey(pk), damus_state: damus_state)
.environmentObject(navigationCoordinator)
}
}
.padding()
+7 -14
View File
@@ -33,13 +33,11 @@ enum ParsedKey {
}
struct LoginView: View {
@State private var create_account = false
@State var key: String = ""
@State var is_pubkey: Bool = false
@State var error: String? = nil
@State private var credential_handler = CredentialHandler()
@Binding var accepted: Bool
@EnvironmentObject var navigationCoordinator: NavigationCoordinator
func get_error(parsed_key: ParsedKey?) -> String? {
if self.error != nil {
@@ -55,12 +53,6 @@ struct LoginView: View {
var body: some View {
ZStack(alignment: .top) {
if accepted {
NavigationLink(destination: CreateAccountView(), isActive: $create_account) {
EmptyView()
}
}
VStack {
SignInHeader()
.padding(.top, 100)
@@ -107,7 +99,8 @@ struct LoginView: View {
.padding(.top, 10)
}
CreateAccountPrompt(create_account: $create_account)
CreateAccountPrompt()
.environmentObject(navigationCoordinator)
.padding(.top, 10)
Spacer()
@@ -337,14 +330,14 @@ struct SignInEntry: View {
}
struct CreateAccountPrompt: View {
@Binding var create_account: Bool
@EnvironmentObject var navigationCoordinator: NavigationCoordinator
var body: some View {
HStack {
Text("New to Nostr?", comment: "Ask the user if they are new to Nostr")
.foregroundColor(Color("DamusMediumGrey"))
Button(NSLocalizedString("Create account", comment: "Button to navigate to create account view.")) {
create_account.toggle()
navigationCoordinator.push(route: Route.CreateAccount)
}
Spacer()
@@ -358,8 +351,8 @@ struct LoginView_Previews: PreviewProvider {
let pubkey = "npub18m76awca3y37hkvuneavuw6pjj4525fw90necxmadrvjg0sdy6qsngq955"
let bech32_pubkey = "KeyInput"
Group {
LoginView(key: pubkey, accepted: .constant(true))
LoginView(key: bech32_pubkey, accepted: .constant(true))
LoginView(key: pubkey)
LoginView(key: bech32_pubkey)
}
}
}
@@ -192,6 +192,8 @@ struct EventGroupView: View {
let state: DamusState
let event: NostrEvent?
let group: EventGroupType
@EnvironmentObject var navigationCoordinator: NavigationCoordinator
func GroupDescription(_ pubkeys: [String]) -> some View {
Text(verbatim: "\(reacting_to_text(profiles: state.profiles, our_pubkey: state.pubkey, group: group, ev: event, pubkeys: pubkeys))")
@@ -237,11 +239,11 @@ struct EventGroupView: View {
let unique_pubkeys = event_group_unique_pubkeys(profiles: state.profiles, group: group)
ProfilePicturesView(state: state, pubkeys: unique_pubkeys)
.environmentObject(navigationCoordinator)
if let event {
let thread = ThreadModel(event: event, damus_state: state)
let dest = ThreadView(state: state, thread: thread)
NavigationLink(destination: dest) {
NavigationLink(value: Route.Thread(thread: thread)) {
VStack(alignment: .leading) {
GroupDescription(unique_pubkeys)
EventBody(damus_state: state, event: event, size: .normal, options: [.truncate_content])
@@ -30,6 +30,8 @@ func notification_item_event(events: EventCache, notif: NotificationItem) -> Sho
struct NotificationItemView: View {
let state: DamusState
let item: NotificationItem
@EnvironmentObject var navigationCoordinator: NavigationCoordinator
var show_item: ShowItem {
notification_item_event(events: state.events, notif: item)
@@ -48,18 +50,22 @@ struct NotificationItemView: View {
switch item {
case .repost(_, let evgrp):
EventGroupView(state: state, event: ev, group: .repost(evgrp))
.environmentObject(navigationCoordinator)
case .event_zap(_, let zapgrp):
EventGroupView(state: state, event: ev, group: .zap(zapgrp))
.environmentObject(navigationCoordinator)
case .profile_zap(let grp):
EventGroupView(state: state, event: nil, group: .profile_zap(grp))
.environmentObject(navigationCoordinator)
case .reaction(_, let evgrp):
EventGroupView(state: state, event: ev, group: .reaction(evgrp))
.environmentObject(navigationCoordinator)
case .reply(let ev):
NavigationLink(destination: ThreadView(state: state, thread: ThreadModel(event: ev, damus_state: state))) {
NavigationLink(value: Route.Thread(thread: ThreadModel(event: ev, damus_state: state))) {
EventView(damus: state, event: ev, options: options)
}
.buttonStyle(.plain)
@@ -89,6 +89,7 @@ struct NotificationsView: View {
@SceneStorage("NotificationsView.filter_state") var filter_state: NotificationFilterState = .all
@Environment(\.colorScheme) var colorScheme
@EnvironmentObject var navigationCoordinator: NavigationCoordinator
var mystery: some View {
VStack(spacing: 20) {
@@ -173,6 +174,7 @@ struct NotificationsView: View {
.frame(height: 5)
ForEach(filter.filter(contacts: state.contacts, items: notifications.notifications), id: \.id) { item in
NotificationItemView(state: state, item: item)
.environmentObject(navigationCoordinator)
}
}
.background(GeometryReader { proxy -> Color in
@@ -11,19 +11,14 @@ struct ProfilePicturesView: View {
let state: DamusState
let pubkeys: [String]
@State var nav_target: String? = nil
@State var navigating: Bool = false
@EnvironmentObject var navigationCoordinator: NavigationCoordinator
var body: some View {
NavigationLink(destination: ProfileView(damus_state: state, pubkey: nav_target ?? ""), isActive: $navigating) {
EmptyView()
}
HStack {
ForEach(pubkeys.prefix(8), id: \.self) { pubkey in
ProfilePicView(pubkey: pubkey, size: 32.0, highlight: .none, profiles: state.profiles, disable_animation: state.settings.disable_animation)
.onTapGesture {
nav_target = pubkey
navigating = true
navigationCoordinator.push(route: Route.ProfileByKey(pubkey: pubkey))
}
}
}
+1 -1
View File
@@ -28,7 +28,7 @@ struct MaybeAnonPfpView: View {
.font(.largeTitle)
.frame(width: size, height: size)
} else {
NavigationLink(destination: ProfileView(damus_state: state, pubkey: pubkey)) {
NavigationLink(value: Route.ProfileByKey(pubkey: pubkey)) {
ProfilePicView(pubkey: pubkey, size: size, highlight: .none, profiles: state.profiles, disable_animation: state.settings.disable_animation)
}
}
+67 -66
View File
@@ -73,11 +73,11 @@ func followedByString(_ friend_intersection: [String], profiles: Profiles, local
struct EditButton: View {
let damus_state: DamusState
@Environment(\.colorScheme) var colorScheme
var body: some View {
NavigationLink(destination: EditMetadataView(damus_state: damus_state)) {
NavigationLink(value: Route.EditMetadata) {
Text("Edit", comment: "Button to edit user's profile.")
.frame(height: 30)
.padding(.horizontal,25)
@@ -92,11 +92,11 @@ struct EditButton: View {
.lineLimit(1)
}
}
func fillColor() -> Color {
colorScheme == .light ? DamusColors.black : DamusColors.white
}
func borderColor() -> Color {
colorScheme == .light ? DamusColors.black : DamusColors.white
}
@@ -104,11 +104,11 @@ struct EditButton: View {
struct VisualEffectView: UIViewRepresentable {
var effect: UIVisualEffect?
func makeUIView(context: UIViewRepresentableContext<Self>) -> UIVisualEffectView {
UIVisualEffectView()
}
func updateUIView(_ uiView: UIVisualEffectView, context: UIViewRepresentableContext<Self>) {
uiView.effect = effect
}
@@ -120,52 +120,54 @@ struct ProfileView: View {
let bannerHeight: CGFloat = 150.0
static let markdown = Markdown()
@State var is_zoomed: Bool = false
@State var show_share_sheet: Bool = false
@State var show_qr_code: Bool = false
@State var action_sheet_presented: Bool = false
@State var filter_state : FilterState = .posts
@State var yOffset: CGFloat = 0
@StateObject var profile: ProfileModel
@StateObject var followers: FollowersModel
@StateObject var zap_button_model: ZapButtonModel = ZapButtonModel()
@EnvironmentObject var navigationCoordinator: NavigationCoordinator
init(damus_state: DamusState, profile: ProfileModel, followers: FollowersModel) {
self.damus_state = damus_state
self._profile = StateObject(wrappedValue: profile)
self._followers = StateObject(wrappedValue: followers)
}
init(damus_state: DamusState, pubkey: String) {
self.damus_state = damus_state
self._profile = StateObject(wrappedValue: ProfileModel(pubkey: pubkey, damus: damus_state))
self._followers = StateObject(wrappedValue: FollowersModel(damus_state: damus_state, target: pubkey))
}
@Environment(\.dismiss) var dismiss
@Environment(\.colorScheme) var colorScheme
@Environment(\.presentationMode) var presentationMode
func imageBorderColor() -> Color {
colorScheme == .light ? DamusColors.white : DamusColors.black
}
func bannerBlurViewOpacity() -> Double {
let progress = -(yOffset + navbarHeight) / 100
return Double(-yOffset > navbarHeight ? progress : 0)
}
var bannerSection: some View {
GeometryReader { proxy -> AnyView in
let minY = proxy.frame(in: .global).minY
DispatchQueue.main.async {
self.yOffset = minY
}
return AnyView(
VStack(spacing: 0) {
ZStack {
@@ -173,10 +175,10 @@ struct ProfileView: View {
.aspectRatio(contentMode: .fill)
.frame(width: proxy.size.width, height: minY > 0 ? bannerHeight + minY : bannerHeight)
.clipped()
VisualEffectView(effect: UIBlurEffect(style: .systemUltraThinMaterial)).opacity(bannerBlurViewOpacity())
}
Divider().opacity(bannerBlurViewOpacity())
}
.frame(height: minY > 0 ? bannerHeight + minY : nil)
@@ -187,11 +189,11 @@ struct ProfileView: View {
.frame(height: bannerHeight)
.allowsHitTesting(false)
}
var navbarHeight: CGFloat {
return 100.0 - (Theme.safeAreaInsets?.top ?? 0)
}
@ViewBuilder
func navImage(img: String) -> some View {
Image(img)
@@ -199,7 +201,7 @@ struct ProfileView: View {
.background(Color.black.opacity(0.6))
.clipShape(Circle())
}
var navBackButton: some View {
Button {
presentationMode.wrappedValue.dismiss()
@@ -207,7 +209,7 @@ struct ProfileView: View {
navImage(img: "chevron-left")
}
}
var navActionSheetButton: some View {
Button(action: {
action_sheet_presented = true
@@ -218,7 +220,7 @@ struct ProfileView: View {
Button(NSLocalizedString("Share", comment: "Button to share the link to a profile.")) {
show_share_sheet = true
}
Button(NSLocalizedString("QR Code", comment: "Button to view profile's qr code.")) {
show_qr_code = true
}
@@ -238,7 +240,7 @@ struct ProfileView: View {
else {
return
}
guard let new_ev = remove_from_mutelist(keypair: keypair, prev: mutelist, to_remove: profile.pubkey) else {
return
}
@@ -254,7 +256,7 @@ struct ProfileView: View {
}
}
}
var customNavbar: some View {
HStack {
navBackButton
@@ -265,7 +267,7 @@ struct ProfileView: View {
.padding(.horizontal)
.accentColor(DamusColors.white)
}
func lnButton(lnurl: String, profile: Profile) -> some View {
let button_img = profile.reactions == false ? "zap.fill" : "zap"
return Button(action: {
@@ -278,7 +280,7 @@ struct ProfileView: View {
if profile.reactions == false {
Text("OnlyZaps Enabled", comment: "Non-tappable text in context menu that shows up when the zap button on profile is long pressed to indicate that the user has enabled OnlyZaps, meaning that they would like to be only zapped and not accept reactions to their notes.")
}
if let addr = profile.lud16 {
Button {
UIPasteboard.general.string = addr
@@ -293,31 +295,30 @@ struct ProfileView: View {
}
}
}
}
.cornerRadius(24)
}
var dmButton: some View {
let dm_model = damus_state.dms.lookup_or_create(profile.pubkey)
let dmview = DMChatView(damus_state: damus_state, dms: dm_model)
return NavigationLink(destination: dmview) {
return NavigationLink(value: Route.DMChat(dms: dm_model)) {
Image("messages")
.profile_button_style(scheme: colorScheme)
}
}
func actionSection(profile_data: Profile?) -> some View {
return Group {
if let profile = profile_data {
if let lnurl = profile.lnurl, lnurl != "" {
lnButton(lnurl: lnurl, profile: profile)
}
}
dmButton
if profile.pubkey != damus_state.pubkey {
FollowButtonView(
target: profile.get_follow_target(),
@@ -325,26 +326,26 @@ struct ProfileView: View {
follow_state: damus_state.contacts.follow_state(profile.pubkey)
)
} else if damus_state.keypair.privkey != nil {
NavigationLink(destination: EditMetadataView(damus_state: damus_state)) {
NavigationLink(value: Route.EditMetadata) {
EditButton(damus_state: damus_state)
}
}
}
}
func pfpOffset() -> CGFloat {
let progress = -yOffset / navbarHeight
let offset = (pfp_size / 4.0) * (progress < 1.0 ? progress : 1)
return offset > 0 ? offset : 0
}
func pfpScale() -> CGFloat {
let progress = -yOffset / navbarHeight
let scale = 1.0 - (0.5 * (progress < 1.0 ? progress : 1))
return scale < 1 ? scale : 1
}
func nameSection(profile_data: Profile?) -> some View {
return Group {
HStack(alignment: .center) {
@@ -358,17 +359,17 @@ struct ProfileView: View {
.fullScreenCover(isPresented: $is_zoomed) {
ProfilePicImageView(pubkey: profile.pubkey, profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation)
}
Spacer()
actionSection(profile_data: profile_data)
}
let follows_you = profile.pubkey != damus_state.pubkey && profile.follows(pubkey: damus_state.pubkey)
ProfileNameView(pubkey: profile.pubkey, profile: profile_data, follows_you: follows_you, damus: damus_state)
}
}
var followersCount: some View {
HStack {
if followers.count == nil {
@@ -385,26 +386,26 @@ struct ProfileView: View {
}
}
}
var aboutSection: some View {
VStack(alignment: .leading, spacing: 8.0) {
let profile_data = damus_state.profiles.lookup(id: profile.pubkey)
nameSection(profile_data: profile_data)
if let about = profile_data?.about {
AboutView(state: damus_state, about: about)
}
if let url = profile_data?.website_url {
WebsiteLink(url: url)
}
HStack {
if let contact = profile.contacts {
let contacts = contact.referenced_pubkeys.map { $0.ref_id }
let following_model = FollowingModel(damus_state: damus_state, contacts: contacts)
NavigationLink(destination: FollowingView(damus_state: damus_state, following: following_model)) {
NavigationLink(value: Route.Following(following: following_model)) {
HStack {
let noun_text = Text(verbatim: "\(followingCountString(profile.following))").font(.subheadline).foregroundColor(.gray)
Text("\(Text(verbatim: profile.following.formatted()).font(.subheadline.weight(.medium))) \(noun_text)", comment: "Sentence composed of 2 variables to describe how many profiles a user is following. In source English, the first variable is the number of profiles being followed, and the second variable is 'Following'.")
@@ -412,10 +413,9 @@ struct ProfileView: View {
}
.buttonStyle(PlainButtonStyle())
}
let fview = FollowersView(damus_state: damus_state)
.environmentObject(followers)
if followers.contacts != nil {
NavigationLink(destination: fview) {
NavigationLink(value: Route.Followers(environmentObject: followers)) {
followersCount
}
.buttonStyle(PlainButtonStyle())
@@ -427,18 +427,18 @@ struct ProfileView: View {
followers.subscribe()
}
}
if let relays = profile.relays {
// Only open relay config view if the user is logged in with private key and they are looking at their own profile.
let noun_text = Text(verbatim: relaysCountString(relays.keys.count)).font(.subheadline).foregroundColor(.gray)
let relay_text = Text("\(Text(verbatim: relays.keys.count.formatted()).font(.subheadline.weight(.medium))) \(noun_text)", comment: "Sentence composed of 2 variables to describe how many relay servers a user is connected. In source English, the first variable is the number of relay servers, and the second variable is 'Relay' or 'Relays'.")
if profile.pubkey == damus_state.pubkey && damus_state.is_privkey_user {
NavigationLink(destination: RelayConfigView(state: damus_state)) {
NavigationLink(value: Route.RelayConfig) {
relay_text
}
.buttonStyle(PlainButtonStyle())
} else {
NavigationLink(destination: UserRelaysView(state: damus_state, relays: Array(relays.keys).sorted())) {
NavigationLink(value: Route.UserRelays(relays: Array(relays.keys).sorted())) {
relay_text
}
.buttonStyle(PlainButtonStyle())
@@ -451,7 +451,7 @@ struct ProfileView: View {
if !friended_followers.isEmpty {
Spacer()
NavigationLink(destination: FollowersYouKnowView(damus_state: damus_state, friended_followers: friended_followers)) {
NavigationLink(value: Route.FollowersYouKnow(friendedFollowers: friended_followers)) {
HStack {
CondensedProfilePicturesView(state: damus_state, pubkeys: friended_followers, maxPictures: 3)
Text(followedByString(friended_followers, profiles: damus_state.profiles))
@@ -464,7 +464,7 @@ struct ProfileView: View {
}
.padding(.horizontal)
}
var body: some View {
ZStack {
ScrollView(.vertical) {
@@ -521,6 +521,7 @@ struct ProfileView: View {
}
.fullScreenCover(isPresented: $show_qr_code) {
QRCodeView(damus_state: damus_state, pubkey: profile.pubkey)
.environmentObject(navigationCoordinator)
}
if damus_state.is_privkey_user {
@@ -542,7 +543,7 @@ struct ProfileView_Previews: PreviewProvider {
func test_damus_state() -> DamusState {
let pubkey = "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"
let damus = DamusState.empty
let prof = Profile(name: "damus", display_name: "damus", about: "iOS app!", picture: "https://damus.io/img/logo.png", banner: "", website: "https://damus.io", lud06: nil, lud16: "jb55@sendsats.lol", nip05: "damus.io", damus_donation: nil)
let tsprof = TimestampedProfile(profile: prof, timestamp: 0, event: test_event)
damus.profiles.add(id: pubkey, profile: tsprof)
@@ -551,15 +552,15 @@ func test_damus_state() -> DamusState {
struct KeyView: View {
let pubkey: String
@Environment(\.colorScheme) var colorScheme
@State private var isCopied = false
func keyColor() -> Color {
colorScheme == .light ? DamusColors.black : DamusColors.white
}
private func copyPubkey(_ pubkey: String) {
UIPasteboard.general.string = pubkey
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
@@ -572,10 +573,10 @@ struct KeyView: View {
}
}
}
var body: some View {
let bech32 = bech32_pubkey(pubkey) ?? pubkey
HStack {
Text(verbatim: "\(abbrev_pubkey(bech32, amount: 16))")
.font(.footnote)
@@ -583,7 +584,7 @@ struct KeyView: View {
.padding(5)
.padding([.leading, .trailing], 5)
.background(RoundedRectangle(cornerRadius: 11).foregroundColor(DamusColors.adaptableGrey))
if isCopied != true {
Button {
copyPubkey(bech32)
+5 -11
View File
@@ -45,12 +45,12 @@ struct QRCodeView: View {
@State var pubkey: String
@Environment(\.presentationMode) var presentationMode
@EnvironmentObject var navigationCoordinator: NavigationCoordinator
@State private var selectedTab = 0
@State var scanResult: ProfileScanResult? = nil
@State var showProfileView: Bool = false
@State var profile: Profile? = nil
@State var error: String? = nil
@@ -209,13 +209,6 @@ struct QRCodeView: View {
Spacer()
if let scanResult {
let dst = ProfileView(damus_state: damus_state, pubkey: scanResult.pubkey)
NavigationLink(destination: dst, isActive: $showProfileView) {
EmptyView()
}
}
Spacer()
Button(action: {
@@ -271,9 +264,10 @@ struct QRCodeView: View {
func show_profile_after_delay() {
DispatchQueue.main.asyncAfter(deadline: .now() + animationDuration) {
showProfileView = true
if let scanResult {
navigationCoordinator.push(route: Route.ProfileByKey(pubkey: scanResult.pubkey))
}
}
}
func cameraAnimate(completion: @escaping () -> Void) {
+2
View File
@@ -10,6 +10,7 @@ import SwiftUI
struct ReactionView: View {
let damus_state: DamusState
let reaction: NostrEvent
@EnvironmentObject var navigationCoordinator: NavigationCoordinator
var content: String {
return to_reaction_emoji(ev: reaction) ?? ""
@@ -22,6 +23,7 @@ struct ReactionView: View {
.frame(width: 50, height: 50)
FollowUserView(target: .pubkey(reaction.pubkey), damus_state: damus_state)
.environmentObject(navigationCoordinator)
}
}
}
+2
View File
@@ -10,6 +10,7 @@ import SwiftUI
struct ReactionsView: View {
let damus_state: DamusState
@StateObject var model: ReactionsModel
@EnvironmentObject var navigationCoordinator: NavigationCoordinator
@Environment(\.dismiss) var dismiss
@@ -18,6 +19,7 @@ struct ReactionsView: View {
LazyVStack {
ForEach(model.events, id: \.id) { ev in
ReactionView(damus_state: damus_state, reaction: ev)
.environmentObject(navigationCoordinator)
}
}
.padding()
@@ -42,9 +42,7 @@ struct RecommendedRelayView: View {
Text(relay).layoutPriority(1)
if let meta = damus.relay_metadata.lookup(relay_id: relay) {
NavigationLink ( destination:
RelayDetailView(state: damus, relay: relay, nip11: meta)
){
NavigationLink(value: Route.RelayDetail(relay: relay, metadata: meta)){
EmptyView()
}
.opacity(0.0)
+3 -1
View File
@@ -72,7 +72,9 @@ struct RelayDetailView: View {
if let pubkey = nip11.pubkey {
Section(NSLocalizedString("Admin", comment: "Label to display relay contact user.")) {
UserViewRow(damus_state: state, pubkey: pubkey)
NavigationLink(value: Route.ProfileByKey(pubkey: pubkey), label: {
UserViewRow(damus_state: state, pubkey: pubkey)
})
}
}
if let relay_connection {
+3 -2
View File
@@ -30,8 +30,9 @@ struct RelayView: View {
if let meta = state.relay_metadata.lookup(relay_id: relay) {
Text(relay)
.background(
NavigationLink("", destination: RelayDetailView(state: state, relay: relay, nip11: meta)).opacity(0.0)
.disabled(showActionButtons)
NavigationLink(value: Route.RelayDetail(relay: relay, metadata: meta), label: {
EmptyView()
}).opacity(0.0).disabled(showActionButtons)
)
Spacer()
+1 -1
View File
@@ -14,7 +14,7 @@ struct SignalView: View {
var body: some View {
Group {
if signal.signal != signal.max_signal {
NavigationLink(destination: RelayConfigView(state: state)) {
NavigationLink(value: Route.RelayConfig) {
Text("\(signal.signal)/\(signal.max_signal)", comment: "Fraction of how many of the user's relay servers that are operational.")
.font(.callout)
.foregroundColor(.gray)
+2
View File
@@ -10,9 +10,11 @@ import SwiftUI
struct RepostView: View {
let damus_state: DamusState
let repost: NostrEvent
@EnvironmentObject var navigationCoordinator: NavigationCoordinator
var body: some View {
FollowUserView(target: .pubkey(repost.pubkey), damus_state: damus_state)
.environmentObject(navigationCoordinator)
}
}
+1 -2
View File
@@ -16,9 +16,8 @@ struct RepostedEvent: View {
var body: some View {
VStack(alignment: .leading) {
let prof = damus.profiles.lookup(id: event.pubkey)
let booster_profile = ProfileView(damus_state: damus, pubkey: event.pubkey)
NavigationLink(destination: booster_profile) {
NavigationLink(value: Route.ProfileByKey(pubkey: event.pubkey)) {
Reposted(damus: damus, pubkey: event.pubkey, profile: prof)
.padding(.horizontal)
}
+2
View File
@@ -10,12 +10,14 @@ import SwiftUI
struct RepostsView: View {
let damus_state: DamusState
@StateObject var model: RepostsModel
@EnvironmentObject var navigationCoordinator: NavigationCoordinator
var body: some View {
ScrollView {
LazyVStack {
ForEach(model.events, id: \.id) { ev in
RepostView(damus_state: damus_state, repost: ev)
.environmentObject(navigationCoordinator)
}
}
.padding()
+4 -4
View File
@@ -24,6 +24,7 @@ struct SearchingEventView: View {
let state: DamusState
let evid: String
let search_type: SearchType
@EnvironmentObject var navigationCoordinator: NavigationCoordinator
@State var search_state: SearchState = .searching
@@ -100,15 +101,14 @@ struct SearchingEventView: View {
.progressViewStyle(.circular)
}
case .found(let ev):
NavigationLink(destination: ThreadView(state: state, thread: ThreadModel(event: ev, damus_state: state))) {
NavigationLink(value: Route.Thread(thread: ThreadModel(event: ev, damus_state: state))) {
EventView(damus: state, event: ev)
}
.buttonStyle(PlainButtonStyle())
case .found_profile(let pk):
NavigationLink(destination: ProfileView(damus_state: state, pubkey: pk)) {
NavigationLink(value: Route.ProfileByKey(pubkey: pk)) {
FollowUserView(target: .pubkey(pk), damus_state: state)
.environmentObject(navigationCoordinator)
}
.buttonStyle(PlainButtonStyle())
case .not_found:
+3
View File
@@ -14,6 +14,7 @@ struct SearchHomeView: View {
@StateObject var model: SearchHomeModel
@State var search: String = ""
@FocusState private var isFocused: Bool
@EnvironmentObject var navigationCoordinator: NavigationCoordinator
let preferredLanguages = Set(Locale.preferredLanguages.map { localeToLanguage($0) })
@@ -67,6 +68,7 @@ struct SearchHomeView: View {
return preferredLanguages.contains(note_lang)
}
)
.environmentObject(navigationCoordinator)
.refreshable {
// Fetch new information by unsubscribing and resubscribing to the relay
model.unsubscribe()
@@ -76,6 +78,7 @@ struct SearchHomeView: View {
var SearchContent: some View {
SearchResultsView(damus_state: damus_state, search: $search)
.environmentObject(navigationCoordinator)
.refreshable {
// Fetch new information by unsubscribing and resubscribing to the relay
model.unsubscribe()
+6 -2
View File
@@ -37,15 +37,16 @@ enum Search: Identifiable {
struct InnerSearchResults: View {
let damus_state: DamusState
let search: Search?
@EnvironmentObject var navigationCoordinator: NavigationCoordinator
func ProfileSearchResult(pk: String) -> some View {
FollowUserView(target: .pubkey(pk), damus_state: damus_state)
.environmentObject(navigationCoordinator)
}
func HashtagSearch(_ ht: String) -> some View {
let search_model = SearchModel(state: damus_state, search: .filter_hashtag([ht]))
let dst = SearchView(appstate: damus_state, search: search_model)
return NavigationLink(destination: dst) {
return NavigationLink(value: Route.Search(search: search_model)) {
Text("Search hashtag: #\(ht)", comment: "Navigation link to search hashtag.")
}
}
@@ -69,6 +70,7 @@ struct InnerSearchResults: View {
case .nip05(let addr):
SearchingEventView(state: damus_state, evid: addr, search_type: .nip05)
.environmentObject(navigationCoordinator)
case .profile(let prof):
let decoded = try? bech32_decode(prof)
@@ -107,10 +109,12 @@ struct SearchResultsView: View {
let damus_state: DamusState
@Binding var search: String
@State var result: Search? = nil
@EnvironmentObject var navigationCoordinator: NavigationCoordinator
var body: some View {
ScrollView {
InnerSearchResults(damus_state: damus_state, search: result)
.environmentObject(navigationCoordinator)
.padding()
}
.frame(maxHeight: .infinity)
+2
View File
@@ -11,9 +11,11 @@ struct SearchView: View {
let appstate: DamusState
@StateObject var search: SearchModel
@Environment(\.dismiss) var dismiss
@EnvironmentObject var navigationCoordinator: NavigationCoordinator
var body: some View {
TimelineView(events: search.events, loading: $search.loading, damus: appstate, show_friend_icon: true, filter: { _ in true })
.environmentObject(navigationCoordinator)
.navigationBarTitle(describe_search(search.search))
.onReceive(handle_notify(.switched_timeline)) { obj in
dismiss()
+6 -7
View File
@@ -17,16 +17,12 @@ func hex_col(r: UInt8, g: UInt8, b: UInt8) -> Color {
struct SetupView: View {
@State private var eula = false
@StateObject var navigationCoordinator: NavigationCoordinator = NavigationCoordinator()
var body: some View {
NavigationView {
NavigationStack(path: $navigationCoordinator.path) {
ZStack {
VStack(alignment: .center) {
NavigationLink(destination: EULAView(), isActive: $eula) {
EmptyView()
}
Spacer()
Image("logo-nobg")
@@ -53,7 +49,7 @@ struct SetupView: View {
Spacer()
Button(action: {
eula.toggle()
navigationCoordinator.push(route: Route.EULA)
}) {
HStack {
Text("Let's get started!", comment: "Button to continue to login page.")
@@ -72,6 +68,9 @@ struct SetupView: View {
.ignoresSafeArea(),
alignment: .top
)
.navigationDestination(for: Route.self) { route in
route.view(navigationCordinator: navigationCoordinator, damusState: DamusState.empty)
}
}
.navigationBarTitleDisplayMode(.inline)
.navigationViewStyle(StackNavigationViewStyle())
+38 -37
View File
@@ -11,23 +11,24 @@ struct SideMenuView: View {
let damus_state: DamusState
@Binding var isSidebarVisible: Bool
@State var confirm_logout: Bool = false
@State private var showQRCode = false
@Environment(\.colorScheme) var colorScheme
@EnvironmentObject var navigationCoordinator: NavigationCoordinator
var sideBarWidth = min(UIScreen.main.bounds.size.width * 0.65, 400.0)
let verticalSpacing: CGFloat = 20
let padding: CGFloat = 30
func fillColor() -> Color {
colorScheme == .light ? DamusColors.white : DamusColors.black
}
func textColor() -> Color {
colorScheme == .light ? DamusColors.black : DamusColors.white
}
var body: some View {
ZStack {
GeometryReader { _ in
@@ -42,20 +43,20 @@ struct SideMenuView: View {
content
}
}
func SidemenuItems(profile_model: ProfileModel, followers: FollowersModel) -> some View {
return VStack(spacing: verticalSpacing) {
NavigationLink(destination: ProfileView(damus_state: damus_state, profile: profile_model, followers: followers)) {
NavigationLink(value: Route.Profile(profile: profile_model, followers: followers)) {
navLabel(title: NSLocalizedString("Profile", comment: "Sidebar menu label for Profile view."), img: "user")
}
NavigationLink(destination: WalletView(damus_state: damus_state, model: damus_state.wallet)) {
NavigationLink(value: Route.Wallet(wallet: damus_state.wallet)) {
navLabel(title: NSLocalizedString("Wallet", comment: "Sidebar menu label for Wallet view."), img: "wallet")
/*
HStack {
Image("wallet")
.tint(DamusColors.adaptableBlack)
Text(NSLocalizedString("wallet", comment: "Sidebar menu label for Wallet view."))
.font(.title2)
.foregroundColor(textColor())
@@ -63,36 +64,35 @@ struct SideMenuView: View {
.dynamicTypeSize(.xSmall)
}*/
}
NavigationLink(destination: MutelistView(damus_state: damus_state, users: get_mutelist_users(damus_state.contacts.mutelist) )) {
NavigationLink(value: Route.MuteList(users: get_mutelist_users(damus_state.contacts.mutelist))) {
navLabel(title: NSLocalizedString("Muted", comment: "Sidebar menu label for muted users view."), img: "mute")
}
NavigationLink(destination: RelayConfigView(state: damus_state)) {
NavigationLink(value: Route.RelayConfig) {
navLabel(title: NSLocalizedString("Relays", comment: "Sidebar menu label for Relays view."), img: "world-relays")
}
NavigationLink(destination: BookmarksView(state: damus_state)) {
NavigationLink(value: Route.Bookmarks) {
navLabel(title: NSLocalizedString("Bookmarks", comment: "Sidebar menu label for Bookmarks view."), img: "bookmark")
}
NavigationLink(destination: ConfigView(state: damus_state)) {
NavigationLink(value: Route.Config) {
navLabel(title: NSLocalizedString("Settings", comment: "Sidebar menu label for accessing the app settings"), img: "settings")
}
}
}
var MainSidemenu: some View {
VStack(alignment: .leading, spacing: 0) {
let profile = damus_state.profiles.lookup(id: damus_state.pubkey)
let followers = FollowersModel(damus_state: damus_state, target: damus_state.pubkey)
let profile_model = ProfileModel(pubkey: damus_state.pubkey, damus: damus_state)
NavigationLink(destination: ProfileView(damus_state: damus_state, profile: profile_model, followers: followers)) {
NavigationLink(value: Route.Profile(profile: profile_model, followers: followers), label: {
HStack {
ProfilePicView(pubkey: damus_state.pubkey, size: 60, highlight: .none, profiles: damus_state.profiles, disable_animation: damus_state.settings.disable_animation)
VStack(alignment: .leading) {
if let display_name = profile?.display_name {
Text(display_name)
@@ -109,10 +109,10 @@ struct SideMenuView: View {
}
}
.padding(.bottom, verticalSpacing)
}
})
Divider()
ScrollView {
SidemenuItems(profile_model: profile_model, followers: followers)
.labelStyle(SideMenuLabelStyle())
@@ -120,21 +120,21 @@ struct SideMenuView: View {
}
}
}
var content: some View {
HStack(alignment: .top) {
ZStack(alignment: .top) {
fillColor()
.ignoresSafeArea()
VStack(alignment: .leading, spacing: 0) {
MainSidemenu
.simultaneousGesture(TapGesture().onEnded {
isSidebarVisible = false
})
Divider()
HStack() {
Button(action: {
//ConfigView(state: damus_state)
@@ -150,9 +150,9 @@ struct SideMenuView: View {
.frame(maxWidth: .infinity, alignment: .leading)
.dynamicTypeSize(.xSmall)
})
Spacer()
Button(action: {
showQRCode.toggle()
}, label: {
@@ -162,6 +162,7 @@ struct SideMenuView: View {
.dynamicTypeSize(.xSmall)
}).fullScreenCover(isPresented: $showQRCode) {
QRCodeView(damus_state: damus_state, pubkey: damus_state.pubkey)
.environmentObject(navigationCoordinator)
}
}
.padding(.top, verticalSpacing)
@@ -186,20 +187,20 @@ struct SideMenuView: View {
Spacer()
}
}
@ViewBuilder
func navLabel(title: String, img: String) -> some View {
Image(img)
.tint(DamusColors.adaptableBlack)
Text(title)
.font(.title2)
.foregroundColor(textColor())
.frame(maxWidth: .infinity, alignment: .leading)
.dynamicTypeSize(.xSmall)
}
struct SideMenuLabelStyle: LabelStyle {
func makeBody(configuration: Configuration) -> some View {
HStack(alignment: .center, spacing: 8) {
+5 -11
View File
@@ -12,8 +12,8 @@ struct InnerTimelineView: View {
@ObservedObject var events: EventHolder
let state: DamusState
let filter: (NostrEvent) -> Bool
@State var nav_target: NostrEvent
@State var navigating: Bool = false
@EnvironmentObject var navigationCoordinator: NavigationCoordinator
static var count: Int = 0
@@ -23,8 +23,6 @@ struct InnerTimelineView: View {
self.filter = filter
print("rendering InnerTimelineView \(InnerTimelineView.count)")
InnerTimelineView.count += 1
// dummy event to avoid MaybeThreadView
self._nav_target = State(initialValue: test_event)
}
var event_options: EventViewOptions {
@@ -36,11 +34,6 @@ struct InnerTimelineView: View {
}
var body: some View {
let thread = ThreadModel(event: nav_target, damus_state: state)
let dest = ThreadView(state: state, thread: thread)
NavigationLink(destination: dest, isActive: $navigating) {
EmptyView()
}
LazyVStack(spacing: 0) {
let events = self.events.events
if events.isEmpty {
@@ -53,8 +46,9 @@ struct InnerTimelineView: View {
let ind = tup.1
EventView(damus: state, event: ev, options: event_options)
.onTapGesture {
nav_target = ev.get_inner_event(cache: state.events) ?? ev
navigating = true
let event = ev.get_inner_event(cache: state.events) ?? ev
let thread = ThreadModel(event: event, damus_state: state)
navigationCoordinator.push(route: Route.Thread(thread: thread))
}
.padding(.top, 7)
.onAppear {
+2
View File
@@ -8,6 +8,7 @@
import SwiftUI
struct TimelineView: View {
@EnvironmentObject var navigationCoordinator: NavigationCoordinator
@ObservedObject var events: EventHolder
@Binding var loading: Bool
@@ -27,6 +28,7 @@ struct TimelineView: View {
.frame(height: 1)
InnerTimelineView(events: events, damus: damus, filter: loading ? { _ in true } : filter)
.environmentObject(navigationCoordinator)
.redacted(reason: loading ? .placeholder : [])
.shimmer(loading)
.disabled(loading)
+3 -6
View File
@@ -9,6 +9,7 @@ import SwiftUI
struct ConnectWalletView: View {
@Environment(\.openURL) private var openURL
@EnvironmentObject var navigationCoordinator: NavigationCoordinator
@ObservedObject var model: WalletModel
@State var scanning: Bool = false
@@ -63,17 +64,13 @@ struct ConnectWalletView: View {
}
var ConnectWallet: some View {
VStack {
NavigationLink(destination: WalletScannerView(result: $wallet_scan_result), isActive: $scanning) {
EmptyView()
}
VStack {
AlbyButton() {
openURL(URL(string:"https://nwc.getalby.com/apps/new?c=Damus")!)
}
BigButton(NSLocalizedString("Attach Wallet", comment: "Text for button to attach Nostr Wallet Connect lightning wallet.")) {
scanning = true
navigationCoordinator.push(route: Route.WalletScanner(result: $wallet_scan_result))
}
if let err = self.error {
+3
View File
@@ -11,6 +11,7 @@ struct WalletView: View {
let damus_state: DamusState
@ObservedObject var model: WalletModel
@ObservedObject var settings: UserSettingsStore
@EnvironmentObject var navigationCoordinator: NavigationCoordinator
init(damus_state: DamusState, model: WalletModel? = nil) {
self.damus_state = damus_state
@@ -156,8 +157,10 @@ struct WalletView: View {
switch model.connect_state {
case .new:
ConnectWalletView(model: model)
.environmentObject(navigationCoordinator)
case .none:
ConnectWalletView(model: model)
.environmentObject(navigationCoordinator)
case .existing(let nwc):
MainWalletView(nwc: nwc)
.onAppear() {