mirror of
git://jb55.com/damus
synced 2024-10-06 11:43:21 +00:00
extract HomeModel from ContentView
huge refactor Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
parent
b230d430ee
commit
097cc54bba
@ -50,6 +50,9 @@
|
|||||||
4C3BEFDC281DCE6100B3DE84 /* Liked.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3BEFDB281DCE6100B3DE84 /* Liked.swift */; };
|
4C3BEFDC281DCE6100B3DE84 /* Liked.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3BEFDB281DCE6100B3DE84 /* Liked.swift */; };
|
||||||
4C3BEFE0281DE1ED00B3DE84 /* DamusState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3BEFDF281DE1ED00B3DE84 /* DamusState.swift */; };
|
4C3BEFE0281DE1ED00B3DE84 /* DamusState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3BEFDF281DE1ED00B3DE84 /* DamusState.swift */; };
|
||||||
4C477C9E282C3A4800033AA3 /* TipCounter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C477C9D282C3A4800033AA3 /* TipCounter.swift */; };
|
4C477C9E282C3A4800033AA3 /* TipCounter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C477C9D282C3A4800033AA3 /* TipCounter.swift */; };
|
||||||
|
4C5F9114283D694D0052CD1C /* FollowTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5F9113283D694D0052CD1C /* FollowTarget.swift */; };
|
||||||
|
4C633350283D40E500B1C9C3 /* HomeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C63334F283D40E500B1C9C3 /* HomeModel.swift */; };
|
||||||
|
4C633352283D419F00B1C9C3 /* SignalModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C633351283D419F00B1C9C3 /* SignalModel.swift */; };
|
||||||
4C75EFA427FA577B0006080F /* PostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFA327FA577B0006080F /* PostView.swift */; };
|
4C75EFA427FA577B0006080F /* PostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFA327FA577B0006080F /* PostView.swift */; };
|
||||||
4C75EFA627FF87A20006080F /* Nostr.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFA527FF87A20006080F /* Nostr.swift */; };
|
4C75EFA627FF87A20006080F /* Nostr.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFA527FF87A20006080F /* Nostr.swift */; };
|
||||||
4C75EFAD28049CFB0006080F /* PostButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFAC28049CFB0006080F /* PostButton.swift */; };
|
4C75EFAD28049CFB0006080F /* PostButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C75EFAC28049CFB0006080F /* PostButton.swift */; };
|
||||||
@ -63,6 +66,9 @@
|
|||||||
4C7FF7D52823313F009601DB /* Mentions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7FF7D42823313F009601DB /* Mentions.swift */; };
|
4C7FF7D52823313F009601DB /* Mentions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C7FF7D42823313F009601DB /* Mentions.swift */; };
|
||||||
4C8682872814DE470026224F /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8682862814DE470026224F /* ProfileView.swift */; };
|
4C8682872814DE470026224F /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8682862814DE470026224F /* ProfileView.swift */; };
|
||||||
4C90BD162839DB54008EE7EF /* NostrMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD152839DB54008EE7EF /* NostrMetadata.swift */; };
|
4C90BD162839DB54008EE7EF /* NostrMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD152839DB54008EE7EF /* NostrMetadata.swift */; };
|
||||||
|
4C90BD18283A9EE5008EE7EF /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD17283A9EE5008EE7EF /* LoginView.swift */; };
|
||||||
|
4C90BD1A283AA67F008EE7EF /* Bech32.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD19283AA67F008EE7EF /* Bech32.swift */; };
|
||||||
|
4C90BD1C283AC38E008EE7EF /* Bech32Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90BD1B283AC38E008EE7EF /* Bech32Tests.swift */; };
|
||||||
4CA2EFA0280E37AC0044ACD8 /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */; };
|
4CA2EFA0280E37AC0044ACD8 /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */; };
|
||||||
4CACA9D5280C31E100D9BBE8 /* ReplyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */; };
|
4CACA9D5280C31E100D9BBE8 /* ReplyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */; };
|
||||||
4CACA9DC280C38C000D9BBE8 /* Profiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CACA9DB280C38C000D9BBE8 /* Profiles.swift */; };
|
4CACA9DC280C38C000D9BBE8 /* Profiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CACA9DB280C38C000D9BBE8 /* Profiles.swift */; };
|
||||||
@ -147,6 +153,9 @@
|
|||||||
4C3BEFDB281DCE6100B3DE84 /* Liked.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Liked.swift; sourceTree = "<group>"; };
|
4C3BEFDB281DCE6100B3DE84 /* Liked.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Liked.swift; sourceTree = "<group>"; };
|
||||||
4C3BEFDF281DE1ED00B3DE84 /* DamusState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusState.swift; sourceTree = "<group>"; };
|
4C3BEFDF281DE1ED00B3DE84 /* DamusState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusState.swift; sourceTree = "<group>"; };
|
||||||
4C477C9D282C3A4800033AA3 /* TipCounter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipCounter.swift; sourceTree = "<group>"; };
|
4C477C9D282C3A4800033AA3 /* TipCounter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipCounter.swift; sourceTree = "<group>"; };
|
||||||
|
4C5F9113283D694D0052CD1C /* FollowTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowTarget.swift; sourceTree = "<group>"; };
|
||||||
|
4C63334F283D40E500B1C9C3 /* HomeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeModel.swift; sourceTree = "<group>"; };
|
||||||
|
4C633351283D419F00B1C9C3 /* SignalModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignalModel.swift; sourceTree = "<group>"; };
|
||||||
4C75EFA327FA577B0006080F /* PostView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostView.swift; sourceTree = "<group>"; };
|
4C75EFA327FA577B0006080F /* PostView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostView.swift; sourceTree = "<group>"; };
|
||||||
4C75EFA527FF87A20006080F /* Nostr.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Nostr.swift; sourceTree = "<group>"; };
|
4C75EFA527FF87A20006080F /* Nostr.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Nostr.swift; sourceTree = "<group>"; };
|
||||||
4C75EFA72804823E0006080F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
4C75EFA72804823E0006080F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||||
@ -161,6 +170,9 @@
|
|||||||
4C7FF7D42823313F009601DB /* Mentions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mentions.swift; sourceTree = "<group>"; };
|
4C7FF7D42823313F009601DB /* Mentions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mentions.swift; sourceTree = "<group>"; };
|
||||||
4C8682862814DE470026224F /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = "<group>"; };
|
4C8682862814DE470026224F /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = "<group>"; };
|
||||||
4C90BD152839DB54008EE7EF /* NostrMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrMetadata.swift; sourceTree = "<group>"; };
|
4C90BD152839DB54008EE7EF /* NostrMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrMetadata.swift; sourceTree = "<group>"; };
|
||||||
|
4C90BD17283A9EE5008EE7EF /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = "<group>"; };
|
||||||
|
4C90BD19283AA67F008EE7EF /* Bech32.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bech32.swift; sourceTree = "<group>"; };
|
||||||
|
4C90BD1B283AC38E008EE7EF /* Bech32Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bech32Tests.swift; sourceTree = "<group>"; };
|
||||||
4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineView.swift; sourceTree = "<group>"; };
|
4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineView.swift; sourceTree = "<group>"; };
|
||||||
4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyView.swift; sourceTree = "<group>"; };
|
4CACA9D4280C31E100D9BBE8 /* ReplyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyView.swift; sourceTree = "<group>"; };
|
||||||
4CACA9DB280C38C000D9BBE8 /* Profiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Profiles.swift; sourceTree = "<group>"; };
|
4CACA9DB280C38C000D9BBE8 /* Profiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Profiles.swift; sourceTree = "<group>"; };
|
||||||
@ -232,6 +244,9 @@
|
|||||||
4C363AA328296DEE006E126D /* SearchModel.swift */,
|
4C363AA328296DEE006E126D /* SearchModel.swift */,
|
||||||
4C3AC79A28306D7B00E1F516 /* Contacts.swift */,
|
4C3AC79A28306D7B00E1F516 /* Contacts.swift */,
|
||||||
4C285C85283892E7008A31F1 /* CreateAccountModel.swift */,
|
4C285C85283892E7008A31F1 /* CreateAccountModel.swift */,
|
||||||
|
4C63334F283D40E500B1C9C3 /* HomeModel.swift */,
|
||||||
|
4C633351283D419F00B1C9C3 /* SignalModel.swift */,
|
||||||
|
4C5F9113283D694D0052CD1C /* FollowTarget.swift */,
|
||||||
);
|
);
|
||||||
path = Models;
|
path = Models;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -268,6 +283,7 @@
|
|||||||
4C285C8328385690008A31F1 /* CreateAccountView.swift */,
|
4C285C8328385690008A31F1 /* CreateAccountView.swift */,
|
||||||
4C285C892838B985008A31F1 /* ProfilePictureSelector.swift */,
|
4C285C892838B985008A31F1 /* ProfilePictureSelector.swift */,
|
||||||
4C285C8D28399BFD008A31F1 /* SaveKeysView.swift */,
|
4C285C8D28399BFD008A31F1 /* SaveKeysView.swift */,
|
||||||
|
4C90BD17283A9EE5008EE7EF /* LoginView.swift */,
|
||||||
);
|
);
|
||||||
path = Views;
|
path = Views;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -302,6 +318,7 @@
|
|||||||
4C363AA728297703006E126D /* InsertSort.swift */,
|
4C363AA728297703006E126D /* InsertSort.swift */,
|
||||||
4C477C9D282C3A4800033AA3 /* TipCounter.swift */,
|
4C477C9D282C3A4800033AA3 /* TipCounter.swift */,
|
||||||
4C285C8B28398BC6008A31F1 /* Keys.swift */,
|
4C285C8B28398BC6008A31F1 /* Keys.swift */,
|
||||||
|
4C90BD19283AA67F008EE7EF /* Bech32.swift */,
|
||||||
);
|
);
|
||||||
path = Util;
|
path = Util;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -355,6 +372,7 @@
|
|||||||
4CE6DEF627F7A08200C66700 /* damusTests */ = {
|
4CE6DEF627F7A08200C66700 /* damusTests */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
4C90BD1B283AC38E008EE7EF /* Bech32Tests.swift */,
|
||||||
4C363A9F2828A8DD006E126D /* LikeTests.swift */,
|
4C363A9F2828A8DD006E126D /* LikeTests.swift */,
|
||||||
4C363A9D2828A822006E126D /* ReplyTests.swift */,
|
4C363A9D2828A822006E126D /* ReplyTests.swift */,
|
||||||
4CE6DEF727F7A08200C66700 /* damusTests.swift */,
|
4CE6DEF727F7A08200C66700 /* damusTests.swift */,
|
||||||
@ -533,6 +551,7 @@
|
|||||||
4C285C8A2838B985008A31F1 /* ProfilePictureSelector.swift in Sources */,
|
4C285C8A2838B985008A31F1 /* ProfilePictureSelector.swift in Sources */,
|
||||||
4C75EFB92804A2740006080F /* EventView.swift in Sources */,
|
4C75EFB92804A2740006080F /* EventView.swift in Sources */,
|
||||||
4C7FF7D52823313F009601DB /* Mentions.swift in Sources */,
|
4C7FF7D52823313F009601DB /* Mentions.swift in Sources */,
|
||||||
|
4C633350283D40E500B1C9C3 /* HomeModel.swift in Sources */,
|
||||||
4C363A9828283441006E126D /* TestingPrivate.swift in Sources */,
|
4C363A9828283441006E126D /* TestingPrivate.swift in Sources */,
|
||||||
4C363A9028247A1D006E126D /* NostrLink.swift in Sources */,
|
4C363A9028247A1D006E126D /* NostrLink.swift in Sources */,
|
||||||
4C0A3F8C280F5FCA000448DE /* ChatroomView.swift in Sources */,
|
4C0A3F8C280F5FCA000448DE /* ChatroomView.swift in Sources */,
|
||||||
@ -544,6 +563,7 @@
|
|||||||
4CA2EFA0280E37AC0044ACD8 /* TimelineView.swift in Sources */,
|
4CA2EFA0280E37AC0044ACD8 /* TimelineView.swift in Sources */,
|
||||||
4C363A8428233689006E126D /* Parser.swift in Sources */,
|
4C363A8428233689006E126D /* Parser.swift in Sources */,
|
||||||
4C363A9A28283854006E126D /* Reply.swift in Sources */,
|
4C363A9A28283854006E126D /* Reply.swift in Sources */,
|
||||||
|
4C90BD18283A9EE5008EE7EF /* LoginView.swift in Sources */,
|
||||||
4C3BEFDC281DCE6100B3DE84 /* Liked.swift in Sources */,
|
4C3BEFDC281DCE6100B3DE84 /* Liked.swift in Sources */,
|
||||||
4C75EFB128049D510006080F /* NostrResponse.swift in Sources */,
|
4C75EFB128049D510006080F /* NostrResponse.swift in Sources */,
|
||||||
4CEE2AF7280B2DEA00AB5EEF /* ProfileName.swift in Sources */,
|
4CEE2AF7280B2DEA00AB5EEF /* ProfileName.swift in Sources */,
|
||||||
@ -558,11 +578,13 @@
|
|||||||
4C3AC7A12835A81400E1F516 /* SetupView.swift in Sources */,
|
4C3AC7A12835A81400E1F516 /* SetupView.swift in Sources */,
|
||||||
4C285C8C28398BC7008A31F1 /* Keys.swift in Sources */,
|
4C285C8C28398BC7008A31F1 /* Keys.swift in Sources */,
|
||||||
4CACA9DC280C38C000D9BBE8 /* Profiles.swift in Sources */,
|
4CACA9DC280C38C000D9BBE8 /* Profiles.swift in Sources */,
|
||||||
|
4C633352283D419F00B1C9C3 /* SignalModel.swift in Sources */,
|
||||||
4C363A94282704FA006E126D /* Post.swift in Sources */,
|
4C363A94282704FA006E126D /* Post.swift in Sources */,
|
||||||
4C363A8828236948006E126D /* BlocksView.swift in Sources */,
|
4C363A8828236948006E126D /* BlocksView.swift in Sources */,
|
||||||
4C75EFAF28049D350006080F /* NostrFilter.swift in Sources */,
|
4C75EFAF28049D350006080F /* NostrFilter.swift in Sources */,
|
||||||
4C363A9C282838B9006E126D /* EventRef.swift in Sources */,
|
4C363A9C282838B9006E126D /* EventRef.swift in Sources */,
|
||||||
4C8682872814DE470026224F /* ProfileView.swift in Sources */,
|
4C8682872814DE470026224F /* ProfileView.swift in Sources */,
|
||||||
|
4C5F9114283D694D0052CD1C /* FollowTarget.swift in Sources */,
|
||||||
4CE6DF1627F8DEBF00C66700 /* RelayConnection.swift in Sources */,
|
4CE6DF1627F8DEBF00C66700 /* RelayConnection.swift in Sources */,
|
||||||
4C3BEFD6281D995700B3DE84 /* ActionBarModel.swift in Sources */,
|
4C3BEFD6281D995700B3DE84 /* ActionBarModel.swift in Sources */,
|
||||||
4C363AA428296DEE006E126D /* SearchModel.swift in Sources */,
|
4C363AA428296DEE006E126D /* SearchModel.swift in Sources */,
|
||||||
@ -578,6 +600,7 @@
|
|||||||
4C3BEFDA281DCA1400B3DE84 /* LikeCounter.swift in Sources */,
|
4C3BEFDA281DCA1400B3DE84 /* LikeCounter.swift in Sources */,
|
||||||
4C3AC79B28306D7B00E1F516 /* Contacts.swift in Sources */,
|
4C3AC79B28306D7B00E1F516 /* Contacts.swift in Sources */,
|
||||||
4C363A8E28236FE4006E126D /* NoteContentView.swift in Sources */,
|
4C363A8E28236FE4006E126D /* NoteContentView.swift in Sources */,
|
||||||
|
4C90BD1A283AA67F008EE7EF /* Bech32.swift in Sources */,
|
||||||
4CACA9D5280C31E100D9BBE8 /* ReplyView.swift in Sources */,
|
4CACA9D5280C31E100D9BBE8 /* ReplyView.swift in Sources */,
|
||||||
4C0A3F97280F8E02000448DE /* ThreadView.swift in Sources */,
|
4C0A3F97280F8E02000448DE /* ThreadView.swift in Sources */,
|
||||||
4C75EFA427FA577B0006080F /* PostView.swift in Sources */,
|
4C75EFA427FA577B0006080F /* PostView.swift in Sources */,
|
||||||
@ -594,6 +617,7 @@
|
|||||||
files = (
|
files = (
|
||||||
4C363A9E2828A822006E126D /* ReplyTests.swift in Sources */,
|
4C363A9E2828A822006E126D /* ReplyTests.swift in Sources */,
|
||||||
4C363AA02828A8DD006E126D /* LikeTests.swift in Sources */,
|
4C363AA02828A8DD006E126D /* LikeTests.swift in Sources */,
|
||||||
|
4C90BD1C283AC38E008EE7EF /* Bech32Tests.swift in Sources */,
|
||||||
4CE6DEF827F7A08200C66700 /* damusTests.swift in Sources */,
|
4CE6DEF827F7A08200C66700 /* damusTests.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
@ -31,28 +31,30 @@ enum ThreadState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
let pubkey: String
|
let keypair: Keypair
|
||||||
let privkey: String
|
|
||||||
|
var pubkey: String {
|
||||||
|
return keypair.pubkey
|
||||||
|
}
|
||||||
|
|
||||||
|
var privkey: String? {
|
||||||
|
return keypair.privkey
|
||||||
|
}
|
||||||
|
|
||||||
@State var status: String = "Not connected"
|
@State var status: String = "Not connected"
|
||||||
@State var active_sheet: Sheets? = nil
|
@State var active_sheet: Sheets? = nil
|
||||||
@State var loading: Bool = true
|
|
||||||
@State var damus_state: DamusState? = nil
|
@State var damus_state: DamusState? = nil
|
||||||
@State var selected_timeline: Timeline? = .home
|
@State var selected_timeline: Timeline? = .home
|
||||||
@State var is_thread_open: Bool = false
|
@State var is_thread_open: Bool = false
|
||||||
@State var is_profile_open: Bool = false
|
@State var is_profile_open: Bool = false
|
||||||
@State var last_event_of_kind: [String: [Int: NostrEvent]] = [:]
|
|
||||||
@State var has_events: [String: ()] = [:]
|
|
||||||
@State var new_notifications: Bool = false
|
|
||||||
@State var event: NostrEvent? = nil
|
@State var event: NostrEvent? = nil
|
||||||
@State var events: [NostrEvent] = []
|
|
||||||
@State var friend_events: [NostrEvent] = []
|
|
||||||
@State var notifications: [NostrEvent] = []
|
|
||||||
@State var active_profile: String? = nil
|
@State var active_profile: String? = nil
|
||||||
@State var active_search: NostrFilter? = nil
|
@State var active_search: NostrFilter? = nil
|
||||||
@State var active_event_id: String? = nil
|
@State var active_event_id: String? = nil
|
||||||
@State var profile_open: Bool = false
|
@State var profile_open: Bool = false
|
||||||
@State var thread_open: Bool = false
|
@State var thread_open: Bool = false
|
||||||
@State var search_open: Bool = false
|
@State var search_open: Bool = false
|
||||||
|
@StateObject var home: HomeModel = HomeModel()
|
||||||
|
|
||||||
// connect retry timer
|
// connect retry timer
|
||||||
let timer = Timer.publish(every: 60, on: .main, in: .common).autoconnect()
|
let timer = Timer.publish(every: 60, on: .main, in: .common).autoconnect()
|
||||||
@ -64,9 +66,10 @@ struct ContentView: View {
|
|||||||
HStack {
|
HStack {
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
if self.loading {
|
if home.signal.signal != home.signal.max_signal {
|
||||||
ProgressView()
|
Text("\(home.signal.signal)/\(home.signal.max_signal)")
|
||||||
.progressViewStyle(.circular)
|
.font(.callout)
|
||||||
|
.foregroundColor(.gray)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,10 +80,12 @@ struct ContentView: View {
|
|||||||
var PostingTimelineView: some View {
|
var PostingTimelineView: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
if let damus = self.damus_state {
|
if let damus = self.damus_state {
|
||||||
TimelineView(events: $friend_events, damus: damus)
|
TimelineView(events: $home.events, damus: damus)
|
||||||
}
|
}
|
||||||
PostButtonContainer {
|
if privkey != nil {
|
||||||
self.active_sheet = .post
|
PostButtonContainer {
|
||||||
|
self.active_sheet = .post
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -105,13 +110,9 @@ struct ContentView: View {
|
|||||||
PostingTimelineView
|
PostingTimelineView
|
||||||
|
|
||||||
case .notifications:
|
case .notifications:
|
||||||
TimelineView(events: $notifications, damus: damus)
|
TimelineView(events: $home.notifications, damus: damus)
|
||||||
.navigationTitle("Notifications")
|
.navigationTitle("Notifications")
|
||||||
|
|
||||||
case .global:
|
|
||||||
|
|
||||||
TimelineView(events: $events, damus: damus)
|
|
||||||
.navigationTitle("Global")
|
|
||||||
case .none:
|
case .none:
|
||||||
EmptyView()
|
EmptyView()
|
||||||
}
|
}
|
||||||
@ -165,7 +166,7 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TabBar(new_notifications: $new_notifications, selected: $selected_timeline, action: switch_timeline)
|
TabBar(new_notifications: $home.new_notifications, selected: $selected_timeline, action: switch_timeline)
|
||||||
}
|
}
|
||||||
.onAppear() {
|
.onAppear() {
|
||||||
self.connect()
|
self.connect()
|
||||||
@ -201,6 +202,10 @@ struct ContentView: View {
|
|||||||
|
|
||||||
}
|
}
|
||||||
.onReceive(handle_notify(.boost)) { notif in
|
.onReceive(handle_notify(.boost)) { notif in
|
||||||
|
guard let privkey = self.privkey else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let ev = notif.object as! NostrEvent
|
let ev = notif.object as! NostrEvent
|
||||||
let boost = make_boost_event(pubkey: pubkey, privkey: privkey, boosted: ev)
|
let boost = make_boost_event(pubkey: pubkey, privkey: privkey, boosted: ev)
|
||||||
self.damus_state?.pool.send(.event(boost))
|
self.damus_state?.pool.send(.event(boost))
|
||||||
@ -215,6 +220,9 @@ struct ContentView: View {
|
|||||||
self.active_sheet = .reply(ev)
|
self.active_sheet = .reply(ev)
|
||||||
}
|
}
|
||||||
.onReceive(handle_notify(.like)) { like in
|
.onReceive(handle_notify(.like)) { like in
|
||||||
|
guard let privkey = self.privkey else {
|
||||||
|
return
|
||||||
|
}
|
||||||
let ev = like.object as! NostrEvent
|
let ev = like.object as! NostrEvent
|
||||||
let like_ev = make_like_event(pubkey: pubkey, privkey: privkey, liked: ev)
|
let like_ev = make_like_event(pubkey: pubkey, privkey: privkey, liked: ev)
|
||||||
self.damus_state?.pool.send(.event(like_ev))
|
self.damus_state?.pool.send(.event(like_ev))
|
||||||
@ -224,6 +232,10 @@ struct ContentView: View {
|
|||||||
self.damus_state?.pool.send(.event(ev))
|
self.damus_state?.pool.send(.event(ev))
|
||||||
}
|
}
|
||||||
.onReceive(handle_notify(.unfollow)) { notif in
|
.onReceive(handle_notify(.unfollow)) { notif in
|
||||||
|
guard let privkey = self.privkey else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let pk = notif.object as! String
|
let pk = notif.object as! String
|
||||||
guard let damus = self.damus_state else {
|
guard let damus = self.damus_state else {
|
||||||
return
|
return
|
||||||
@ -235,12 +247,16 @@ struct ContentView: View {
|
|||||||
privkey: privkey,
|
privkey: privkey,
|
||||||
unfollow: pk) {
|
unfollow: pk) {
|
||||||
notify(.unfollowed, pk)
|
notify(.unfollowed, pk)
|
||||||
damus.contacts.friends.remove(pk)
|
damus.contacts.remove_friend(pk)
|
||||||
//friend_events = friend_events.filter { $0.pubkey != pk }
|
//friend_events = friend_events.filter { $0.pubkey != pk }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onReceive(handle_notify(.follow)) { notif in
|
.onReceive(handle_notify(.follow)) { notif in
|
||||||
let pk = notif.object as! String
|
guard let privkey = self.privkey else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let fnotify = notif.object as! FollowTarget
|
||||||
guard let damus = self.damus_state else {
|
guard let damus = self.damus_state else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -249,12 +265,22 @@ struct ContentView: View {
|
|||||||
our_contacts: damus.contacts.event,
|
our_contacts: damus.contacts.event,
|
||||||
pubkey: damus.pubkey,
|
pubkey: damus.pubkey,
|
||||||
privkey: privkey,
|
privkey: privkey,
|
||||||
follow: ReferencedId(ref_id: pk, relay_id: nil, key: "p")) {
|
follow: ReferencedId(ref_id: fnotify.pubkey, relay_id: nil, key: "p")) {
|
||||||
notify(.followed, pk)
|
notify(.followed, fnotify.pubkey)
|
||||||
damus.contacts.friends.insert(pk)
|
|
||||||
|
switch fnotify {
|
||||||
|
case .pubkey(let pk):
|
||||||
|
damus.contacts.add_friend_pubkey(pk)
|
||||||
|
case .contact(let ev):
|
||||||
|
damus.contacts.add_friend_contact(ev)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onReceive(handle_notify(.post)) { obj in
|
.onReceive(handle_notify(.post)) { obj in
|
||||||
|
guard let privkey = self.privkey else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let post_res = obj.object as! NostrPostResult
|
let post_res = obj.object as! NostrPostResult
|
||||||
switch post_res {
|
switch post_res {
|
||||||
case .post(let post):
|
case .post(let post):
|
||||||
@ -268,12 +294,11 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
.onReceive(timer) { n in
|
.onReceive(timer) { n in
|
||||||
self.damus_state?.pool.connect_to_disconnected()
|
self.damus_state?.pool.connect_to_disconnected()
|
||||||
self.loading = (self.damus_state?.pool.num_connecting ?? 0) != 0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func is_friend_event(_ ev: NostrEvent) -> Bool {
|
func is_friend_event(_ ev: NostrEvent) -> Bool {
|
||||||
return damus.is_friend_event(ev, our_pubkey: self.pubkey, friends: self.damus_state!.contacts.friends)
|
return damus.is_friend_event(ev, our_pubkey: self.pubkey, contacts: self.damus_state!.contacts)
|
||||||
}
|
}
|
||||||
|
|
||||||
func switch_timeline(_ timeline: Timeline) {
|
func switch_timeline(_ timeline: Timeline) {
|
||||||
@ -283,7 +308,7 @@ struct ContentView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (timeline != .notifications && self.selected_timeline == .notifications) || timeline == .notifications {
|
if (timeline != .notifications && self.selected_timeline == .notifications) || timeline == .notifications {
|
||||||
new_notifications = false
|
home.new_notifications = false
|
||||||
}
|
}
|
||||||
self.selected_timeline = timeline
|
self.selected_timeline = timeline
|
||||||
NotificationCenter.default.post(name: .switched_timeline, object: timeline)
|
NotificationCenter.default.post(name: .switched_timeline, object: timeline)
|
||||||
@ -312,9 +337,9 @@ struct ContentView: View {
|
|||||||
add_relay(pool, "wss://nostr-relay.freeberty.net")
|
add_relay(pool, "wss://nostr-relay.freeberty.net")
|
||||||
add_relay(pool, "wss://nostr-relay.untethr.me")
|
add_relay(pool, "wss://nostr-relay.untethr.me")
|
||||||
|
|
||||||
pool.register_handler(sub_id: sub_id, handler: handle_event)
|
pool.register_handler(sub_id: sub_id, handler: home.handle_event)
|
||||||
|
|
||||||
self.damus_state = DamusState(pool: pool, pubkey: pubkey,
|
self.damus_state = DamusState(pool: pool, keypair: keypair,
|
||||||
likes: EventCounter(our_pubkey: pubkey),
|
likes: EventCounter(our_pubkey: pubkey),
|
||||||
boosts: EventCounter(our_pubkey: pubkey),
|
boosts: EventCounter(our_pubkey: pubkey),
|
||||||
contacts: Contacts(),
|
contacts: Contacts(),
|
||||||
@ -322,239 +347,12 @@ struct ContentView: View {
|
|||||||
image_cache: ImageCache(),
|
image_cache: ImageCache(),
|
||||||
profiles: Profiles()
|
profiles: Profiles()
|
||||||
)
|
)
|
||||||
|
home.damus_state = self.damus_state!
|
||||||
|
|
||||||
pool.connect()
|
pool.connect()
|
||||||
}
|
}
|
||||||
|
|
||||||
func handle_contact_event(_ ev: NostrEvent) {
|
|
||||||
if ev.pubkey == self.pubkey {
|
|
||||||
damus_state!.contacts.event = ev
|
|
||||||
// our contacts
|
|
||||||
for tag in ev.tags {
|
|
||||||
if tag.count > 1 && tag[0] == "p" {
|
|
||||||
damus_state!.contacts.friends.insert(tag[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func handle_boost_event(_ ev: NostrEvent) {
|
|
||||||
var boost_ev_id = ev.last_refid()?.ref_id
|
|
||||||
|
|
||||||
// CHECK SIGS ON THESE
|
|
||||||
if let inner_ev = ev.inner_event {
|
|
||||||
boost_ev_id = inner_ev.id
|
|
||||||
|
|
||||||
if inner_ev.kind == 1 {
|
|
||||||
handle_text_event(ev)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let e = boost_ev_id else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch damus_state!.boosts.add_event(ev, target: e) {
|
|
||||||
case .already_counted:
|
|
||||||
break
|
|
||||||
case .success(let n):
|
|
||||||
let boosted = Counted(event: ev, id: e, total: n)
|
|
||||||
notify(.boosted, boosted)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func handle_like_event(_ ev: NostrEvent) {
|
|
||||||
guard let e = ev.last_refid() else {
|
|
||||||
// no id ref? invalid like event
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// CHECK SIGS ON THESE
|
|
||||||
|
|
||||||
switch damus_state!.likes.add_event(ev, target: e.ref_id) {
|
|
||||||
case .already_counted:
|
|
||||||
break
|
|
||||||
case .success(let n):
|
|
||||||
let liked = Counted(event: ev, id: e.ref_id, total: n)
|
|
||||||
notify(.liked, liked)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func handle_metadata_event(_ ev: NostrEvent) {
|
|
||||||
guard let profile: Profile = decode_data(Data(ev.content.utf8)) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if let mprof = damus_state!.profiles.lookup_with_timestamp(id: ev.pubkey) {
|
|
||||||
if mprof.timestamp > ev.created_at {
|
|
||||||
// skip if we already have an newer profile
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let tprof = TimestampedProfile(profile: profile, timestamp: ev.created_at)
|
|
||||||
damus_state!.profiles.add(id: ev.pubkey, profile: tprof)
|
|
||||||
|
|
||||||
notify(.profile_updated, ProfileUpdate(pubkey: ev.pubkey, profile: profile))
|
|
||||||
}
|
|
||||||
|
|
||||||
func get_last_event_of_kind(relay_id: String, kind: Int) -> NostrEvent? {
|
|
||||||
guard let m = last_event_of_kind[relay_id] else {
|
|
||||||
last_event_of_kind[relay_id] = [:]
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return m[kind]
|
|
||||||
}
|
|
||||||
|
|
||||||
func send_filters(relay_id: String) {
|
|
||||||
// TODO: since times should be based on events from a specific relay
|
|
||||||
// perhaps we could mark this in the relay pool somehow
|
|
||||||
let text_filter = NostrFilter.filter_kinds([1,5,6,7])
|
|
||||||
let profile_filter = NostrFilter.filter_profiles
|
|
||||||
var contacts_filter = NostrFilter.filter_contacts
|
|
||||||
|
|
||||||
contacts_filter.authors = [self.pubkey]
|
|
||||||
|
|
||||||
var filters = [text_filter, profile_filter, contacts_filter]
|
|
||||||
|
|
||||||
filters = update_filters_with_since(last_of_kind: last_event_of_kind[relay_id] ?? [:], filters: filters)
|
|
||||||
|
|
||||||
print("connected to \(relay_id) with filters:")
|
|
||||||
for filter in filters {
|
|
||||||
print(filter)
|
|
||||||
}
|
|
||||||
print("-----")
|
|
||||||
|
|
||||||
self.damus_state?.pool.send(.subscribe(.init(filters: filters, sub_id: sub_id)), to: [relay_id])
|
|
||||||
//self.pool?.send(.subscribe(.init(filters: [notification_filter], sub_id: "notifications")))
|
|
||||||
}
|
|
||||||
|
|
||||||
func handle_notification(ev: NostrEvent) {
|
|
||||||
notifications.append(ev)
|
|
||||||
notifications = notifications.sorted { $0.created_at > $1.created_at }
|
|
||||||
|
|
||||||
let last_notified = get_last_notified()
|
|
||||||
|
|
||||||
if last_notified == nil || last_notified!.created_at < ev.created_at {
|
|
||||||
save_last_notified(ev)
|
|
||||||
new_notifications = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func handle_friend_event(_ ev: NostrEvent) {
|
|
||||||
if !is_friend_event(ev) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !insert_uniq_sorted_event(events: &self.friend_events, new_ev: ev, cmp: { $0.created_at > $1.created_at } ) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func handle_text_event(_ ev: NostrEvent) {
|
|
||||||
if should_hide_event(ev) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !insert_uniq_sorted_event(events: &self.events, new_ev: ev, cmp: { $0.created_at > $1.created_at }) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
handle_friend_event(ev)
|
|
||||||
|
|
||||||
if is_notification(ev: ev, pubkey: pubkey) {
|
|
||||||
handle_notification(ev: ev)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func process_event(relay_id: String, ev: NostrEvent) {
|
|
||||||
if has_events[ev.id] != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
has_events[ev.id] = ()
|
|
||||||
let last_k = get_last_event_of_kind(relay_id: relay_id, kind: ev.kind)
|
|
||||||
if last_k == nil || ev.created_at > last_k!.created_at {
|
|
||||||
last_event_of_kind[relay_id]?[ev.kind] = ev
|
|
||||||
}
|
|
||||||
if ev.kind == 1 {
|
|
||||||
handle_text_event(ev)
|
|
||||||
} else if ev.kind == 0 {
|
|
||||||
handle_metadata_event(ev)
|
|
||||||
} else if ev.kind == 6 {
|
|
||||||
handle_boost_event(ev)
|
|
||||||
} else if ev.kind == 7 {
|
|
||||||
handle_like_event(ev)
|
|
||||||
} else if ev.kind == 3 {
|
|
||||||
handle_contact_event(ev)
|
|
||||||
|
|
||||||
if ev.pubkey == pubkey {
|
|
||||||
process_friend_events()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func process_friend_events() {
|
|
||||||
for event in events {
|
|
||||||
handle_friend_event(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func handle_event(relay_id: String, conn_event: NostrConnectionEvent) {
|
|
||||||
switch conn_event {
|
|
||||||
case .ws_event(let ev):
|
|
||||||
|
|
||||||
/*
|
|
||||||
if let wsev = ws_nostr_event(relay: relay_id, ev: ev) {
|
|
||||||
wsev.flags |= 1
|
|
||||||
self.events.insert(wsev, at: 0)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
switch ev {
|
|
||||||
case .connected:
|
|
||||||
send_filters(relay_id: relay_id)
|
|
||||||
case .error(let merr):
|
|
||||||
let desc = merr.debugDescription
|
|
||||||
if desc.contains("Software caused connection abort") {
|
|
||||||
self.damus_state?.pool.reconnect(to: [relay_id])
|
|
||||||
}
|
|
||||||
case .disconnected: fallthrough
|
|
||||||
case .cancelled:
|
|
||||||
self.damus_state?.pool.reconnect(to: [relay_id])
|
|
||||||
case .reconnectSuggested(let t):
|
|
||||||
if t {
|
|
||||||
self.damus_state?.pool.reconnect(to: [relay_id])
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
self.loading = (self.damus_state?.pool.num_connecting ?? 0) != 0
|
|
||||||
|
|
||||||
print("ws_event \(ev)")
|
|
||||||
|
|
||||||
case .nostr_event(let ev):
|
|
||||||
switch ev {
|
|
||||||
case .event(let sub_id, let ev):
|
|
||||||
// globally handle likes
|
|
||||||
let always_process = ev.known_kind == .like || ev.known_kind == .contacts || ev.known_kind == .metadata
|
|
||||||
if !always_process && sub_id != self.sub_id {
|
|
||||||
// TODO: other views like threads might have their own sub ids, so ignore those events... or should we?
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
self.process_event(relay_id: relay_id, ev: ev)
|
|
||||||
case .notice(let msg):
|
|
||||||
self.events.insert(NostrEvent(content: "NOTICE from \(relay_id): \(msg)", pubkey: "system"), at: 0)
|
|
||||||
print(msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func should_hide_event(_ ev: NostrEvent) -> Bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -574,9 +372,9 @@ func get_metadata_since_time(_ metadata_event: NostrEvent?) -> Int64? {
|
|||||||
return metadata_event!.created_at - 60 * 10
|
return metadata_event!.created_at - 60 * 10
|
||||||
}
|
}
|
||||||
|
|
||||||
func get_since_time(last_event: NostrEvent?) -> Int64 {
|
func get_since_time(last_event: NostrEvent?) -> Int64? {
|
||||||
if last_event == nil {
|
if last_event == nil {
|
||||||
return Int64(Date().timeIntervalSince1970) - (24 * 60 * 60 * 3)
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return last_event!.created_at - 60 * 10
|
return last_event!.created_at - 60 * 10
|
||||||
@ -696,7 +494,7 @@ func update_filters_with_since(last_of_kind: [Int: NostrEvent], filters: [NostrF
|
|||||||
return since
|
return since
|
||||||
}
|
}
|
||||||
|
|
||||||
return since! < earliest! ? since! : earliest!
|
return earliest.flatMap { earliest in since.map { since in since < earliest ? since : earliest } }
|
||||||
}
|
}
|
||||||
|
|
||||||
if let earliest = earliest {
|
if let earliest = earliest {
|
||||||
|
@ -9,9 +9,47 @@ import Foundation
|
|||||||
|
|
||||||
|
|
||||||
class Contacts {
|
class Contacts {
|
||||||
var friends: Set<String> = Set()
|
private var friends: Set<String> = Set()
|
||||||
|
private var friend_of_friends: Set<String> = Set()
|
||||||
var event: NostrEvent?
|
var event: NostrEvent?
|
||||||
|
|
||||||
|
func get_friendosphere() -> [String] {
|
||||||
|
var fs = get_friend_list()
|
||||||
|
fs.append(contentsOf: get_friend_of_friend_list())
|
||||||
|
return fs
|
||||||
|
}
|
||||||
|
|
||||||
|
func remove_friend(_ pubkey: String) {
|
||||||
|
friends.remove(pubkey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func get_friend_list() -> [String] {
|
||||||
|
return Array(friends)
|
||||||
|
}
|
||||||
|
|
||||||
|
func get_friend_of_friend_list() -> [String] {
|
||||||
|
return Array(friend_of_friends)
|
||||||
|
}
|
||||||
|
|
||||||
|
func add_friend_pubkey(_ pubkey: String) {
|
||||||
|
friends.insert(pubkey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func add_friend_contact(_ contact: NostrEvent) {
|
||||||
|
friends.insert(contact.pubkey)
|
||||||
|
for friend in contact.referenced_pubkeys {
|
||||||
|
friend_of_friends.insert(friend.ref_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func is_friend_of_friend(_ pubkey: String) -> Bool {
|
||||||
|
return friend_of_friends.contains(pubkey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func is_in_friendosphere(_ pubkey: String) -> Bool {
|
||||||
|
return friends.contains(pubkey) || friend_of_friends.contains(pubkey)
|
||||||
|
}
|
||||||
|
|
||||||
func is_friend(_ pubkey: String) -> Bool {
|
func is_friend(_ pubkey: String) -> Bool {
|
||||||
return friends.contains(pubkey)
|
return friends.contains(pubkey)
|
||||||
}
|
}
|
||||||
@ -121,9 +159,9 @@ func make_contact_relays(_ relays: [RelayDescriptor]) -> [String: RelayInfo] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: tests for this
|
// TODO: tests for this
|
||||||
func is_friend_event(_ ev: NostrEvent, our_pubkey: String, friends: Set<String>) -> Bool
|
func is_friend_event(_ ev: NostrEvent, our_pubkey: String, contacts: Contacts) -> Bool
|
||||||
{
|
{
|
||||||
if !friends.contains(ev.pubkey) {
|
if !contacts.is_friend(ev.pubkey) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,7 +171,7 @@ func is_friend_event(_ ev: NostrEvent, our_pubkey: String, friends: Set<String>)
|
|||||||
|
|
||||||
// show our replies?
|
// show our replies?
|
||||||
for pk in ev.referenced_pubkeys {
|
for pk in ev.referenced_pubkeys {
|
||||||
if friends.contains(pk.ref_id) {
|
if contacts.is_friend(pk.ref_id) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,14 @@ class CreateAccountModel: ObservableObject {
|
|||||||
@Published var pubkey: String = ""
|
@Published var pubkey: String = ""
|
||||||
@Published var privkey: String = ""
|
@Published var privkey: String = ""
|
||||||
|
|
||||||
|
var pubkey_bech32: String {
|
||||||
|
return bech32_pubkey(self.pubkey) ?? ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var privkey_bech32: String {
|
||||||
|
return bech32_privkey(self.privkey) ?? ""
|
||||||
|
}
|
||||||
|
|
||||||
var rendered_name: String {
|
var rendered_name: String {
|
||||||
if real_name.isEmpty {
|
if real_name.isEmpty {
|
||||||
return nick_name
|
return nick_name
|
||||||
@ -29,13 +37,13 @@ class CreateAccountModel: ObservableObject {
|
|||||||
init() {
|
init() {
|
||||||
let keypair = generate_new_keypair()
|
let keypair = generate_new_keypair()
|
||||||
self.pubkey = keypair.pubkey
|
self.pubkey = keypair.pubkey
|
||||||
self.privkey = keypair.privkey
|
self.privkey = keypair.privkey!
|
||||||
}
|
}
|
||||||
|
|
||||||
init(real: String, nick: String, about: String) {
|
init(real: String, nick: String, about: String) {
|
||||||
let keypair = generate_new_keypair()
|
let keypair = generate_new_keypair()
|
||||||
self.pubkey = keypair.pubkey
|
self.pubkey = keypair.pubkey
|
||||||
self.privkey = keypair.privkey
|
self.privkey = keypair.privkey!
|
||||||
|
|
||||||
self.real_name = real
|
self.real_name = real
|
||||||
self.nick_name = nick
|
self.nick_name = nick
|
||||||
|
@ -9,11 +9,19 @@ import Foundation
|
|||||||
|
|
||||||
struct DamusState {
|
struct DamusState {
|
||||||
let pool: RelayPool
|
let pool: RelayPool
|
||||||
let pubkey: String
|
let keypair: Keypair
|
||||||
let likes: EventCounter
|
let likes: EventCounter
|
||||||
let boosts: EventCounter
|
let boosts: EventCounter
|
||||||
let contacts: Contacts
|
let contacts: Contacts
|
||||||
let tips: TipCounter
|
let tips: TipCounter
|
||||||
let image_cache: ImageCache
|
let image_cache: ImageCache
|
||||||
let profiles: Profiles
|
let profiles: Profiles
|
||||||
|
|
||||||
|
var pubkey: String {
|
||||||
|
return keypair.pubkey
|
||||||
|
}
|
||||||
|
|
||||||
|
static var empty: DamusState {
|
||||||
|
return DamusState.init(pool: RelayPool(), keypair: Keypair(pubkey: "", privkey: ""), likes: EventCounter(our_pubkey: ""), boosts: EventCounter(our_pubkey: ""), contacts: Contacts(), tips: TipCounter(our_pubkey: ""), image_cache: ImageCache(), profiles: Profiles())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
25
damus/Models/FollowTarget.swift
Normal file
25
damus/Models/FollowTarget.swift
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
//
|
||||||
|
// FollowNotify.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by William Casarin on 2022-05-24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
|
||||||
|
enum FollowTarget {
|
||||||
|
case pubkey(String)
|
||||||
|
case contact(NostrEvent)
|
||||||
|
|
||||||
|
var pubkey: String {
|
||||||
|
switch self {
|
||||||
|
case .pubkey(let pk):
|
||||||
|
return pk
|
||||||
|
case .contact(let ev):
|
||||||
|
return ev.pubkey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
322
damus/Models/HomeModel.swift
Normal file
322
damus/Models/HomeModel.swift
Normal file
@ -0,0 +1,322 @@
|
|||||||
|
//
|
||||||
|
// HomeModel.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by William Casarin on 2022-05-24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
|
||||||
|
class HomeModel: ObservableObject {
|
||||||
|
var damus_state: DamusState
|
||||||
|
|
||||||
|
var has_events: Set<String> = Set()
|
||||||
|
var last_event_of_kind: [String: [Int: NostrEvent]] = [:]
|
||||||
|
var done_init: Bool = false
|
||||||
|
|
||||||
|
let damus_home_subid = UUID().description
|
||||||
|
let damus_contacts_subid = UUID().description
|
||||||
|
let damus_init_subid = UUID().description
|
||||||
|
|
||||||
|
@Published var new_notifications: Bool = false
|
||||||
|
@Published var notifications: [NostrEvent] = []
|
||||||
|
@Published var events: [NostrEvent] = []
|
||||||
|
@Published var signal: SignalModel = SignalModel()
|
||||||
|
|
||||||
|
init() {
|
||||||
|
self.damus_state = DamusState.empty
|
||||||
|
}
|
||||||
|
|
||||||
|
init(damus_state: DamusState) {
|
||||||
|
self.damus_state = damus_state
|
||||||
|
}
|
||||||
|
|
||||||
|
var pool: RelayPool {
|
||||||
|
return damus_state.pool
|
||||||
|
}
|
||||||
|
|
||||||
|
func process_event(sub_id: String, relay_id: String, ev: NostrEvent) {
|
||||||
|
if has_events.contains(ev.id) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
has_events.insert(ev.id)
|
||||||
|
let last_k = get_last_event_of_kind(relay_id: relay_id, kind: ev.kind)
|
||||||
|
if last_k == nil || ev.created_at > last_k!.created_at {
|
||||||
|
last_event_of_kind[relay_id]?[ev.kind] = ev
|
||||||
|
}
|
||||||
|
if ev.kind == 1 {
|
||||||
|
handle_text_event(ev)
|
||||||
|
} else if ev.kind == 0 {
|
||||||
|
handle_metadata_event(ev)
|
||||||
|
} else if ev.kind == 6 {
|
||||||
|
handle_boost_event(ev)
|
||||||
|
} else if ev.kind == 7 {
|
||||||
|
handle_like_event(ev)
|
||||||
|
} else if ev.kind == 3 {
|
||||||
|
handle_contact_event(sub_id: sub_id, relay_id: relay_id, ev: ev)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handle_contact_event(sub_id: String, relay_id: String, ev: NostrEvent) {
|
||||||
|
load_our_contacts(contacts: self.damus_state.contacts, our_pubkey: self.damus_state.pubkey, ev: ev)
|
||||||
|
|
||||||
|
if sub_id == damus_init_subid {
|
||||||
|
pool.send(.unsubscribe(damus_init_subid), to: [relay_id])
|
||||||
|
if !done_init {
|
||||||
|
done_init = true
|
||||||
|
send_home_filters(relay_id: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handle_boost_event(_ ev: NostrEvent) {
|
||||||
|
var boost_ev_id = ev.last_refid()?.ref_id
|
||||||
|
|
||||||
|
// CHECK SIGS ON THESE
|
||||||
|
if let inner_ev = ev.inner_event {
|
||||||
|
boost_ev_id = inner_ev.id
|
||||||
|
|
||||||
|
if inner_ev.kind == 1 {
|
||||||
|
handle_text_event(ev)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let e = boost_ev_id else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch self.damus_state.boosts.add_event(ev, target: e) {
|
||||||
|
case .already_counted:
|
||||||
|
break
|
||||||
|
case .success(let n):
|
||||||
|
let boosted = Counted(event: ev, id: e, total: n)
|
||||||
|
notify(.boosted, boosted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handle_like_event(_ ev: NostrEvent) {
|
||||||
|
guard let e = ev.last_refid() else {
|
||||||
|
// no id ref? invalid like event
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHECK SIGS ON THESE
|
||||||
|
|
||||||
|
switch damus_state.likes.add_event(ev, target: e.ref_id) {
|
||||||
|
case .already_counted:
|
||||||
|
break
|
||||||
|
case .success(let n):
|
||||||
|
let liked = Counted(event: ev, id: e.ref_id, total: n)
|
||||||
|
notify(.liked, liked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func handle_event(relay_id: String, conn_event: NostrConnectionEvent) {
|
||||||
|
switch conn_event {
|
||||||
|
case .ws_event(let ev):
|
||||||
|
|
||||||
|
/*
|
||||||
|
if let wsev = ws_nostr_event(relay: relay_id, ev: ev) {
|
||||||
|
wsev.flags |= 1
|
||||||
|
self.events.insert(wsev, at: 0)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
switch ev {
|
||||||
|
case .connected:
|
||||||
|
if !done_init {
|
||||||
|
send_initial_filters(relay_id: relay_id)
|
||||||
|
} else {
|
||||||
|
send_home_filters(relay_id: relay_id)
|
||||||
|
}
|
||||||
|
case .error(let merr):
|
||||||
|
let desc = merr.debugDescription
|
||||||
|
if desc.contains("Software caused connection abort") {
|
||||||
|
pool.reconnect(to: [relay_id])
|
||||||
|
}
|
||||||
|
case .disconnected: fallthrough
|
||||||
|
case .cancelled:
|
||||||
|
pool.reconnect(to: [relay_id])
|
||||||
|
case .reconnectSuggested(let t):
|
||||||
|
if t {
|
||||||
|
pool.reconnect(to: [relay_id])
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
update_signal_from_pool(signal: self.signal, pool: self.pool)
|
||||||
|
|
||||||
|
print("ws_event \(ev)")
|
||||||
|
|
||||||
|
case .nostr_event(let ev):
|
||||||
|
switch ev {
|
||||||
|
case .event(let sub_id, let ev):
|
||||||
|
// globally handle likes
|
||||||
|
let always_process = sub_id == damus_contacts_subid || sub_id == damus_home_subid || sub_id == damus_init_subid || ev.known_kind == .like || ev.known_kind == .contacts || ev.known_kind == .metadata
|
||||||
|
if !always_process {
|
||||||
|
// TODO: other views like threads might have their own sub ids, so ignore those events... or should we?
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.process_event(sub_id: sub_id, relay_id: relay_id, ev: ev)
|
||||||
|
case .notice(let msg):
|
||||||
|
//self.events.insert(NostrEvent(content: "NOTICE from \(relay_id): \(msg)", pubkey: "system"), at: 0)
|
||||||
|
print(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Send the initial filters, just our contact list mostly
|
||||||
|
func send_initial_filters(relay_id: String) {
|
||||||
|
var filter = NostrFilter.filter_contacts
|
||||||
|
filter.authors = [self.damus_state.pubkey]
|
||||||
|
filter.limit = 1
|
||||||
|
|
||||||
|
pool.send(.subscribe(.init(filters: [filter], sub_id: damus_init_subid)), to: [relay_id])
|
||||||
|
}
|
||||||
|
|
||||||
|
func send_home_filters(relay_id: String?) {
|
||||||
|
// TODO: since times should be based on events from a specific relay
|
||||||
|
// perhaps we could mark this in the relay pool somehow
|
||||||
|
|
||||||
|
var friends = damus_state.contacts.get_friend_list()
|
||||||
|
friends.append(damus_state.pubkey)
|
||||||
|
|
||||||
|
var contacts_filter = NostrFilter.filter_kinds([0,3])
|
||||||
|
contacts_filter.authors = damus_state.contacts.get_friendosphere()
|
||||||
|
|
||||||
|
// TODO: separate likes?
|
||||||
|
var home_filter = NostrFilter.filter_kinds([
|
||||||
|
NostrKind.text.rawValue,
|
||||||
|
NostrKind.like.rawValue,
|
||||||
|
NostrKind.boost.rawValue,
|
||||||
|
])
|
||||||
|
// include our pubkey as well even if we're not technically a friend
|
||||||
|
home_filter.authors = friends
|
||||||
|
home_filter.limit = 1000
|
||||||
|
|
||||||
|
var home_filters = [home_filter]
|
||||||
|
var contacts_filters = [contacts_filter]
|
||||||
|
let last_of_k = relay_id.flatMap { last_event_of_kind[$0] } ?? [:]
|
||||||
|
home_filters = update_filters_with_since(last_of_kind: last_of_k, filters: home_filters)
|
||||||
|
contacts_filters = update_filters_with_since(last_of_kind: last_of_k, filters: contacts_filters)
|
||||||
|
|
||||||
|
print_filters(relay_id: relay_id, filters: [home_filters, contacts_filters])
|
||||||
|
|
||||||
|
if let relay_id = relay_id {
|
||||||
|
pool.send(.subscribe(.init(filters: home_filters, sub_id: damus_home_subid)), to: [relay_id])
|
||||||
|
pool.send(.subscribe(.init(filters: contacts_filters, sub_id: damus_contacts_subid)), to: [relay_id])
|
||||||
|
} else {
|
||||||
|
pool.send(.subscribe(.init(filters: home_filters, sub_id: damus_home_subid)))
|
||||||
|
pool.send(.subscribe(.init(filters: contacts_filters, sub_id: damus_contacts_subid)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handle_metadata_event(_ ev: NostrEvent) {
|
||||||
|
guard let profile: Profile = decode_data(Data(ev.content.utf8)) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let mprof = damus_state.profiles.lookup_with_timestamp(id: ev.pubkey) {
|
||||||
|
if mprof.timestamp > ev.created_at {
|
||||||
|
// skip if we already have an newer profile
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let tprof = TimestampedProfile(profile: profile, timestamp: ev.created_at)
|
||||||
|
damus_state.profiles.add(id: ev.pubkey, profile: tprof)
|
||||||
|
|
||||||
|
notify(.profile_updated, ProfileUpdate(pubkey: ev.pubkey, profile: profile))
|
||||||
|
}
|
||||||
|
|
||||||
|
func get_last_event_of_kind(relay_id: String, kind: Int) -> NostrEvent? {
|
||||||
|
guard let m = last_event_of_kind[relay_id] else {
|
||||||
|
last_event_of_kind[relay_id] = [:]
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return m[kind]
|
||||||
|
}
|
||||||
|
|
||||||
|
func handle_notification(ev: NostrEvent) {
|
||||||
|
if !insert_uniq_sorted_event(events: ¬ifications, new_ev: ev, cmp: { $0.created_at > $1.created_at }) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let last_notified = get_last_notified()
|
||||||
|
|
||||||
|
if last_notified == nil || last_notified!.created_at < ev.created_at {
|
||||||
|
save_last_notified(ev)
|
||||||
|
new_notifications = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func insert_home_event(_ ev: NostrEvent) -> Bool {
|
||||||
|
let ok = insert_uniq_sorted_event(events: &self.events, new_ev: ev, cmp: { $0.created_at > $1.created_at })
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func should_hide_event(_ ev: NostrEvent) -> Bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func handle_text_event(_ ev: NostrEvent) {
|
||||||
|
if should_hide_event(ev) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = insert_home_event(ev)
|
||||||
|
|
||||||
|
if is_notification(ev: ev, pubkey: self.damus_state.pubkey) {
|
||||||
|
handle_notification(ev: ev)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func update_signal_from_pool(signal: SignalModel, pool: RelayPool) {
|
||||||
|
if signal.max_signal != pool.relays.count {
|
||||||
|
signal.max_signal = pool.relays.count
|
||||||
|
}
|
||||||
|
|
||||||
|
if signal.signal != pool.num_connecting {
|
||||||
|
signal.signal = signal.max_signal - pool.num_connecting
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func load_our_contacts(contacts: Contacts, our_pubkey: String, ev: NostrEvent) {
|
||||||
|
if ev.pubkey != our_pubkey {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
contacts.event = ev
|
||||||
|
|
||||||
|
// our contacts
|
||||||
|
for tag in ev.tags {
|
||||||
|
if tag.count > 1 && tag[0] == "p" {
|
||||||
|
// TODO: validate pubkey?
|
||||||
|
contacts.add_friend_pubkey(tag[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
func print_filters(relay_id: String?, filters groups: [[NostrFilter]]) {
|
||||||
|
let relays = relay_id ?? "relays"
|
||||||
|
print("connected to \(relays) with filters:")
|
||||||
|
for group in groups {
|
||||||
|
for filter in group {
|
||||||
|
print(filter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print("-----")
|
||||||
|
}
|
@ -18,6 +18,13 @@ class ProfileModel: ObservableObject {
|
|||||||
var seen_event: Set<String> = Set()
|
var seen_event: Set<String> = Set()
|
||||||
var sub_id = UUID().description
|
var sub_id = UUID().description
|
||||||
|
|
||||||
|
func get_follow_target() -> FollowTarget {
|
||||||
|
if let contacts = contacts {
|
||||||
|
return .contact(contacts)
|
||||||
|
}
|
||||||
|
return .pubkey(pubkey)
|
||||||
|
}
|
||||||
|
|
||||||
init(pubkey: String, damus: DamusState) {
|
init(pubkey: String, damus: DamusState) {
|
||||||
self.pubkey = pubkey
|
self.pubkey = pubkey
|
||||||
self.damus = damus
|
self.damus = damus
|
||||||
@ -39,7 +46,7 @@ class ProfileModel: ObservableObject {
|
|||||||
|
|
||||||
var filter = NostrFilter.filter_authors([pubkey])
|
var filter = NostrFilter.filter_authors([pubkey])
|
||||||
filter.kinds = kinds
|
filter.kinds = kinds
|
||||||
filter.limit = 500
|
filter.limit = 1000
|
||||||
|
|
||||||
print("subscribing to profile \(pubkey) with sub_id \(sub_id)")
|
print("subscribing to profile \(pubkey) with sub_id \(sub_id)")
|
||||||
damus.pool.subscribe(sub_id: sub_id, filters: [filter], handler: handle_event)
|
damus.pool.subscribe(sub_id: sub_id, filters: [filter], handler: handle_event)
|
||||||
|
32
damus/Models/SignalModel.swift
Normal file
32
damus/Models/SignalModel.swift
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
//
|
||||||
|
// SignalModel.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by William Casarin on 2022-05-24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
|
||||||
|
class SignalModel: ObservableObject {
|
||||||
|
@Published var signal: Int
|
||||||
|
@Published var max_signal: Int
|
||||||
|
|
||||||
|
var percentage: Double {
|
||||||
|
if max_signal == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return Double(signal) / Double(max_signal)
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
self.signal = 0
|
||||||
|
self.max_signal = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
init(signal: Int, max_signal: Int) {
|
||||||
|
self.signal = signal
|
||||||
|
self.max_signal = max_signal
|
||||||
|
}
|
||||||
|
}
|
@ -349,22 +349,33 @@ func get_referenced_ids(tags: [[String]], key: String) -> [ReferencedId] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func make_first_contact_event(keypair: Keypair) -> NostrEvent {
|
func make_first_contact_event(keypair: Keypair) -> NostrEvent? {
|
||||||
|
guard let privkey = keypair.privkey else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
let rw_relay_info = RelayInfo(read: true, write: true)
|
let rw_relay_info = RelayInfo(read: true, write: true)
|
||||||
let damus_relay = "wss://relay.damus.io"
|
|
||||||
let relays: [String: RelayInfo] = ["wss://relay.damus.io": rw_relay_info]
|
let relays: [String: RelayInfo] = ["wss://relay.damus.io": rw_relay_info]
|
||||||
let relay_json = encode_json(relays)!
|
let relay_json = encode_json(relays)!
|
||||||
let damus_pubkey = "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"
|
let damus_pubkey = "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"
|
||||||
|
let tags = [
|
||||||
|
["p", damus_pubkey],
|
||||||
|
["p", keypair.pubkey] // you're a friend of yourself!
|
||||||
|
]
|
||||||
let ev = NostrEvent(content: relay_json,
|
let ev = NostrEvent(content: relay_json,
|
||||||
pubkey: keypair.pubkey,
|
pubkey: keypair.pubkey,
|
||||||
kind: NostrKind.contacts.rawValue,
|
kind: NostrKind.contacts.rawValue,
|
||||||
tags: [["p", damus_pubkey, damus_relay]])
|
tags: tags)
|
||||||
ev.calculate_id()
|
ev.calculate_id()
|
||||||
ev.sign(privkey: keypair.privkey)
|
ev.sign(privkey: privkey)
|
||||||
return ev
|
return ev
|
||||||
}
|
}
|
||||||
|
|
||||||
func make_metadata_event(keypair: Keypair, metadata: NostrMetadata) -> NostrEvent {
|
func make_metadata_event(keypair: Keypair, metadata: NostrMetadata) -> NostrEvent? {
|
||||||
|
guard let privkey = keypair.privkey else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
let metadata_json = encode_json(metadata)!
|
let metadata_json = encode_json(metadata)!
|
||||||
let ev = NostrEvent(content: metadata_json,
|
let ev = NostrEvent(content: metadata_json,
|
||||||
pubkey: keypair.pubkey,
|
pubkey: keypair.pubkey,
|
||||||
@ -372,7 +383,7 @@ func make_metadata_event(keypair: Keypair, metadata: NostrMetadata) -> NostrEven
|
|||||||
tags: [])
|
tags: [])
|
||||||
|
|
||||||
ev.calculate_id()
|
ev.calculate_id()
|
||||||
ev.sign(privkey: keypair.privkey)
|
ev.sign(privkey: privkey)
|
||||||
return ev
|
return ev
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,6 +75,9 @@ func char_to_hex(_ c: UInt8) -> UInt8?
|
|||||||
|
|
||||||
func hex_decode(_ str: String) -> [UInt8]?
|
func hex_decode(_ str: String) -> [UInt8]?
|
||||||
{
|
{
|
||||||
|
if str.count == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
var ret: [UInt8] = []
|
var ret: [UInt8] = []
|
||||||
let chars = Array(str.utf8)
|
let chars = Array(str.utf8)
|
||||||
var i: Int = 0
|
var i: Int = 0
|
||||||
|
207
damus/Util/Bech32.swift
Normal file
207
damus/Util/Bech32.swift
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
//
|
||||||
|
// Bech32.swift
|
||||||
|
//
|
||||||
|
// Modified by William Casarin in 2022
|
||||||
|
// Created by Evolution Group Ltd on 12.02.2018.
|
||||||
|
// Copyright © 2018 Evolution Group Ltd. All rights reserved.
|
||||||
|
//
|
||||||
|
// Base32 address format for native v0-16 witness outputs implementation
|
||||||
|
// https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki
|
||||||
|
// Inspired by Pieter Wuille C++ implementation
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// Bech32 checksum implementation
|
||||||
|
fileprivate let gen: [UInt32] = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
|
||||||
|
/// Bech32 checksum delimiter
|
||||||
|
fileprivate let checksumMarker: String = "1"
|
||||||
|
/// Bech32 character set for encoding
|
||||||
|
fileprivate let encCharset: Data = "qpzry9x8gf2tvdw0s3jn54khce6mua7l".data(using: .utf8)!
|
||||||
|
/// Bech32 character set for decoding
|
||||||
|
fileprivate let decCharset: [Int8] = [
|
||||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
|
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||||
|
15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1,
|
||||||
|
-1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1,
|
||||||
|
1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1,
|
||||||
|
-1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1,
|
||||||
|
1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1
|
||||||
|
]
|
||||||
|
|
||||||
|
/// Find the polynomial with value coefficients mod the generator as 30-bit.
|
||||||
|
public func bech32_polymod(_ values: Data) -> UInt32 {
|
||||||
|
var chk: UInt32 = 1
|
||||||
|
for v in values {
|
||||||
|
let top = (chk >> 25)
|
||||||
|
chk = (chk & 0x1ffffff) << 5 ^ UInt32(v)
|
||||||
|
for i: UInt8 in 0..<5 {
|
||||||
|
chk ^= ((top >> i) & 1) == 0 ? 0 : gen[Int(i)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return chk
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Expand a HRP for use in checksum computation.
|
||||||
|
func bech32_expand_hrp(_ s: String) -> Data {
|
||||||
|
var left: [UInt8] = []
|
||||||
|
var right: [UInt8] = []
|
||||||
|
for x in Array(s) {
|
||||||
|
let scalars = String(x).unicodeScalars
|
||||||
|
left.append(UInt8(scalars[scalars.startIndex].value) >> 5)
|
||||||
|
right.append(UInt8(scalars[scalars.startIndex].value) & 31)
|
||||||
|
}
|
||||||
|
return Data(left + [0] + right)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Verify checksum
|
||||||
|
public func bech32_verify(hrp: String, checksum: Data) -> Bool {
|
||||||
|
var data = bech32_expand_hrp(hrp)
|
||||||
|
data.append(checksum)
|
||||||
|
return bech32_polymod(data) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create checksum
|
||||||
|
public func bech32_create_checksum(hrp: String, values: Data) -> Data {
|
||||||
|
var enc = bech32_expand_hrp(hrp)
|
||||||
|
enc.append(values)
|
||||||
|
enc.append(Data(repeating: 0x00, count: 6))
|
||||||
|
let mod: UInt32 = bech32_polymod(enc) ^ 1
|
||||||
|
var ret: Data = Data(repeating: 0x00, count: 6)
|
||||||
|
for i in 0..<6 {
|
||||||
|
ret[i] = UInt8((mod >> (5 * (5 - i))) & 31)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
public func bech32_encode(hrp: String, _ input: [UInt8]) -> String {
|
||||||
|
let table: [Character] = Array("qpzry9x8gf2tvdw0s3jn54khce6mua7l")
|
||||||
|
let bits = eightToFiveBits(input)
|
||||||
|
let check_sum = bech32_checksum(hrp: hrp, data: bits)
|
||||||
|
let separator = "1"
|
||||||
|
return "\(hrp)" + separator + String((bits + check_sum).map { table[Int($0)] })
|
||||||
|
}
|
||||||
|
|
||||||
|
func bech32_checksum(hrp: String, data: [UInt8]) -> [UInt8] {
|
||||||
|
let values = bech32_expand_hrp(hrp) + data
|
||||||
|
let polymod = bech32_polymod(values + [0,0,0,0,0,0]) ^ 1
|
||||||
|
var result: [UInt32] = []
|
||||||
|
for i in (0..<6) {
|
||||||
|
result.append((polymod >> (5 * (5 - UInt32(i)))) & 31)
|
||||||
|
}
|
||||||
|
return result.map { UInt8($0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
func eightToFiveBits(_ input: [UInt8]) -> [UInt8] {
|
||||||
|
guard !input.isEmpty else { return [] }
|
||||||
|
|
||||||
|
var outputSize = (input.count * 8) / 5
|
||||||
|
if ((input.count * 8) % 5) != 0 {
|
||||||
|
outputSize += 1
|
||||||
|
}
|
||||||
|
var outputArray: [UInt8] = []
|
||||||
|
for i in (0..<outputSize) {
|
||||||
|
let devision = (i * 5) / 8
|
||||||
|
let reminder = (i * 5) % 8
|
||||||
|
var element = input[devision] << reminder
|
||||||
|
element >>= 3
|
||||||
|
|
||||||
|
if (reminder > 3) && (i + 1 < outputSize) {
|
||||||
|
element = element | (input[devision + 1] >> (8 - reminder + 3))
|
||||||
|
}
|
||||||
|
|
||||||
|
outputArray.append(element)
|
||||||
|
}
|
||||||
|
|
||||||
|
return outputArray
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decode Bech32 string
|
||||||
|
public func bech32_decode(_ str: String) throws -> (hrp: String, data: Data) {
|
||||||
|
guard let strBytes = str.data(using: .utf8) else {
|
||||||
|
throw Bech32Error.nonUTF8String
|
||||||
|
}
|
||||||
|
guard strBytes.count <= 90 else {
|
||||||
|
throw Bech32Error.stringLengthExceeded
|
||||||
|
}
|
||||||
|
var lower: Bool = false
|
||||||
|
var upper: Bool = false
|
||||||
|
for c in strBytes {
|
||||||
|
// printable range
|
||||||
|
if c < 33 || c > 126 {
|
||||||
|
throw Bech32Error.nonPrintableCharacter
|
||||||
|
}
|
||||||
|
// 'a' to 'z'
|
||||||
|
if c >= 97 && c <= 122 {
|
||||||
|
lower = true
|
||||||
|
}
|
||||||
|
// 'A' to 'Z'
|
||||||
|
if c >= 65 && c <= 90 {
|
||||||
|
upper = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if lower && upper {
|
||||||
|
throw Bech32Error.invalidCase
|
||||||
|
}
|
||||||
|
guard let pos = str.range(of: checksumMarker, options: .backwards)?.lowerBound else {
|
||||||
|
throw Bech32Error.noChecksumMarker
|
||||||
|
}
|
||||||
|
let intPos: Int = str.distance(from: str.startIndex, to: pos)
|
||||||
|
guard intPos >= 1 else {
|
||||||
|
throw Bech32Error.incorrectHrpSize
|
||||||
|
}
|
||||||
|
guard intPos + 7 <= str.count else {
|
||||||
|
throw Bech32Error.incorrectChecksumSize
|
||||||
|
}
|
||||||
|
let vSize: Int = str.count - 1 - intPos
|
||||||
|
var values: Data = Data(repeating: 0x00, count: vSize)
|
||||||
|
for i in 0..<vSize {
|
||||||
|
let c = strBytes[i + intPos + 1]
|
||||||
|
let decInt = decCharset[Int(c)]
|
||||||
|
if decInt == -1 {
|
||||||
|
throw Bech32Error.invalidCharacter
|
||||||
|
}
|
||||||
|
values[i] = UInt8(decInt)
|
||||||
|
}
|
||||||
|
let hrp = String(str[..<pos]).lowercased()
|
||||||
|
guard bech32_verify(hrp: hrp, checksum: values) else {
|
||||||
|
throw Bech32Error.checksumMismatch
|
||||||
|
}
|
||||||
|
return (hrp, Data(values[..<(vSize-6)]))
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Bech32Error: LocalizedError {
|
||||||
|
case nonUTF8String
|
||||||
|
case nonPrintableCharacter
|
||||||
|
case invalidCase
|
||||||
|
case noChecksumMarker
|
||||||
|
case incorrectHrpSize
|
||||||
|
case incorrectChecksumSize
|
||||||
|
case stringLengthExceeded
|
||||||
|
|
||||||
|
case invalidCharacter
|
||||||
|
case checksumMismatch
|
||||||
|
|
||||||
|
public var errorDescription: String? {
|
||||||
|
switch self {
|
||||||
|
case .checksumMismatch:
|
||||||
|
return "Checksum doesn't match"
|
||||||
|
case .incorrectChecksumSize:
|
||||||
|
return "Checksum size too low"
|
||||||
|
case .incorrectHrpSize:
|
||||||
|
return "Human-readable-part is too small or empty"
|
||||||
|
case .invalidCase:
|
||||||
|
return "String contains mixed case characters"
|
||||||
|
case .invalidCharacter:
|
||||||
|
return "Invalid character met on decoding"
|
||||||
|
case .noChecksumMarker:
|
||||||
|
return "Checksum delimiter not found"
|
||||||
|
case .nonPrintableCharacter:
|
||||||
|
return "Non printable character in input string"
|
||||||
|
case .nonUTF8String:
|
||||||
|
return "String cannot be decoded by utf8 decoder"
|
||||||
|
case .stringLengthExceeded:
|
||||||
|
return "Input string is too long"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,16 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
func insert_uniq<T: Equatable>(xs: inout [T], new_x: T) -> Bool {
|
||||||
|
for x in xs {
|
||||||
|
if x == new_x {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xs.append(new_x)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func insert_uniq_sorted_event(events: inout [NostrEvent], new_ev: NostrEvent, cmp: (NostrEvent, NostrEvent) -> Bool) -> Bool {
|
func insert_uniq_sorted_event(events: inout [NostrEvent], new_ev: NostrEvent, cmp: (NostrEvent, NostrEvent) -> Bool) -> Bool {
|
||||||
var i: Int = 0
|
var i: Int = 0
|
||||||
|
@ -8,9 +8,54 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import secp256k1
|
import secp256k1
|
||||||
|
|
||||||
|
let PUBKEY_HRP = "npub"
|
||||||
|
let PRIVKEY_HRP = "nsec"
|
||||||
|
|
||||||
struct Keypair {
|
struct Keypair {
|
||||||
let pubkey: String
|
let pubkey: String
|
||||||
let privkey: String
|
let privkey: String?
|
||||||
|
|
||||||
|
var pubkey_bech32: String {
|
||||||
|
return bech32_pubkey(pubkey)!
|
||||||
|
}
|
||||||
|
|
||||||
|
var privkey_bech32: String? {
|
||||||
|
return privkey.flatMap { bech32_privkey($0) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Bech32Key {
|
||||||
|
case pub(String)
|
||||||
|
case sec(String)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decode_bech32_key(_ key: String) -> Bech32Key? {
|
||||||
|
guard let decoded = try? bech32_decode(key) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let hexed = hex_encode(decoded.data)
|
||||||
|
if decoded.hrp == "npub" {
|
||||||
|
return .pub(hexed)
|
||||||
|
} else if decoded.hrp == "nsec" {
|
||||||
|
return .sec(hexed)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func bech32_privkey(_ privkey: String) -> String? {
|
||||||
|
guard let bytes = hex_decode(privkey) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return bech32_encode(hrp: "nsec", bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func bech32_pubkey(_ pubkey: String) -> String? {
|
||||||
|
guard let bytes = hex_decode(pubkey) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return bech32_encode(hrp: "npub", bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func generate_new_keypair() -> Keypair {
|
func generate_new_keypair() -> Keypair {
|
||||||
@ -21,16 +66,37 @@ func generate_new_keypair() -> Keypair {
|
|||||||
return Keypair(pubkey: pubkey, privkey: privkey)
|
return Keypair(pubkey: pubkey, privkey: privkey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func save_keypair(pubkey: String, privkey: String) {
|
func privkey_to_pubkey(privkey: String) -> String? {
|
||||||
|
guard let sec = hex_decode(privkey) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
guard let key = try? secp256k1.Signing.PrivateKey(rawRepresentation: sec) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return hex_encode(Data(key.publicKey.xonlyKeyBytes))
|
||||||
|
}
|
||||||
|
|
||||||
|
func save_pubkey(pubkey: String) {
|
||||||
UserDefaults.standard.set(pubkey, forKey: "pubkey")
|
UserDefaults.standard.set(pubkey, forKey: "pubkey")
|
||||||
|
}
|
||||||
|
|
||||||
|
func save_privkey(privkey: String) {
|
||||||
UserDefaults.standard.set(privkey, forKey: "privkey")
|
UserDefaults.standard.set(privkey, forKey: "privkey")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func clear_privkey() {
|
||||||
|
UserDefaults.standard.removeObject(forKey: "privkey")
|
||||||
|
}
|
||||||
|
|
||||||
|
func save_keypair(pubkey: String, privkey: String) {
|
||||||
|
save_pubkey(pubkey: pubkey)
|
||||||
|
save_privkey(privkey: privkey)
|
||||||
|
}
|
||||||
|
|
||||||
func get_saved_keypair() -> Keypair? {
|
func get_saved_keypair() -> Keypair? {
|
||||||
get_saved_pubkey().flatMap { pubkey in
|
get_saved_pubkey().flatMap { pubkey in
|
||||||
get_saved_privkey().map { privkey in
|
let privkey = get_saved_privkey()
|
||||||
return Keypair(pubkey: pubkey, privkey: privkey)
|
return Keypair(pubkey: pubkey, privkey: privkey)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,7 +115,7 @@ struct ChatView: View {
|
|||||||
if is_active || next_ev == nil || next_ev!.pubkey != event.pubkey {
|
if is_active || next_ev == nil || next_ev!.pubkey != event.pubkey {
|
||||||
let bar = make_actionbar_model(ev: event, damus: damus)
|
let bar = make_actionbar_model(ev: event, damus: damus)
|
||||||
EventActionBar(event: event,
|
EventActionBar(event: event,
|
||||||
our_pubkey: damus.pubkey,
|
keypair: damus.keypair,
|
||||||
profiles: damus.profiles,
|
profiles: damus.profiles,
|
||||||
bar: bar
|
bar: bar
|
||||||
)
|
)
|
||||||
|
@ -12,32 +12,6 @@ struct CreateAccountView: View {
|
|||||||
@State var is_light: Bool = false
|
@State var is_light: Bool = false
|
||||||
@State var is_done: Bool = false
|
@State var is_done: Bool = false
|
||||||
|
|
||||||
func FormTextInput(_ title: String, text: Binding<String>) -> some View {
|
|
||||||
return TextField("", text: text)
|
|
||||||
.placeholder(when: text.wrappedValue.isEmpty) {
|
|
||||||
Text(title).foregroundColor(.white.opacity(0.4))
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
.background {
|
|
||||||
RoundedRectangle(cornerRadius: 4.0).opacity(0.2)
|
|
||||||
}
|
|
||||||
.foregroundColor(.white)
|
|
||||||
.font(.body.bold())
|
|
||||||
}
|
|
||||||
|
|
||||||
func FormLabel(_ title: String, optional: Bool = false) -> some View {
|
|
||||||
return HStack {
|
|
||||||
Text(title)
|
|
||||||
.bold()
|
|
||||||
.foregroundColor(.white)
|
|
||||||
if optional {
|
|
||||||
Text("optional")
|
|
||||||
.font(.callout)
|
|
||||||
.foregroundColor(.white.opacity(0.5))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func SignupForm<FormContent: View>(@ViewBuilder content: () -> FormContent) -> some View {
|
func SignupForm<FormContent: View>(@ViewBuilder content: () -> FormContent) -> some View {
|
||||||
return VStack(alignment: .leading, spacing: 10.0, content: content)
|
return VStack(alignment: .leading, spacing: 10.0, content: content)
|
||||||
}
|
}
|
||||||
@ -45,7 +19,7 @@ struct CreateAccountView: View {
|
|||||||
func regen_key() {
|
func regen_key() {
|
||||||
let keypair = generate_new_keypair()
|
let keypair = generate_new_keypair()
|
||||||
self.account.pubkey = keypair.pubkey
|
self.account.pubkey = keypair.pubkey
|
||||||
self.account.privkey = keypair.privkey
|
self.account.privkey = keypair.privkey!
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@ -89,7 +63,7 @@ struct CreateAccountView: View {
|
|||||||
regen_key()
|
regen_key()
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyInput($account.pubkey)
|
KeyText($account.pubkey)
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
regen_key()
|
regen_key()
|
||||||
}
|
}
|
||||||
@ -110,6 +84,20 @@ struct CreateAccountView: View {
|
|||||||
|
|
||||||
}
|
}
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.navigationBarBackButtonHidden(true)
|
||||||
|
.navigationBarItems(leading: BackNav())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BackNav: View {
|
||||||
|
@Environment(\.dismiss) var dismiss
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Image(systemName: "chevron.backward")
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.onTapGesture {
|
||||||
|
self.dismiss()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,10 +121,38 @@ struct CreateAccountView_Previews: PreviewProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func KeyInput(_ text: Binding<String>) -> some View {
|
func KeyText(_ text: Binding<String>) -> some View {
|
||||||
return Text("\(text.wrappedValue)")
|
let decoded = hex_decode(text.wrappedValue)!
|
||||||
|
let bechkey = bech32_encode(hrp: PUBKEY_HRP, decoded)
|
||||||
|
return Text(bechkey)
|
||||||
.textSelection(.enabled)
|
.textSelection(.enabled)
|
||||||
.font(.callout.monospaced())
|
.font(.callout.monospaced())
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func FormTextInput(_ title: String, text: Binding<String>) -> some View {
|
||||||
|
return TextField("", text: text)
|
||||||
|
.placeholder(when: text.wrappedValue.isEmpty) {
|
||||||
|
Text(title).foregroundColor(.white.opacity(0.4))
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
.background {
|
||||||
|
RoundedRectangle(cornerRadius: 4.0).opacity(0.2)
|
||||||
|
}
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.font(.body.bold())
|
||||||
|
}
|
||||||
|
|
||||||
|
func FormLabel(_ title: String, optional: Bool = false) -> some View {
|
||||||
|
return HStack {
|
||||||
|
Text(title)
|
||||||
|
.bold()
|
||||||
|
.foregroundColor(.white)
|
||||||
|
if optional {
|
||||||
|
Text("optional")
|
||||||
|
.font(.callout)
|
||||||
|
.foregroundColor(.white.opacity(0.5))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ enum ActionBarSheet: Identifiable {
|
|||||||
|
|
||||||
struct EventActionBar: View {
|
struct EventActionBar: View {
|
||||||
let event: NostrEvent
|
let event: NostrEvent
|
||||||
let our_pubkey: String
|
let keypair: Keypair
|
||||||
@State var sheet: ActionBarSheet? = nil
|
@State var sheet: ActionBarSheet? = nil
|
||||||
let profiles: Profiles
|
let profiles: Profiles
|
||||||
@StateObject var bar: ActionBarModel
|
@StateObject var bar: ActionBarModel
|
||||||
@ -34,10 +34,12 @@ struct EventActionBar: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
*/
|
*/
|
||||||
EventActionButton(img: "bubble.left", col: nil) {
|
if keypair.privkey != nil {
|
||||||
notify(.reply, event)
|
EventActionButton(img: "bubble.left", col: nil) {
|
||||||
|
notify(.reply, event)
|
||||||
|
}
|
||||||
|
.padding([.trailing], 20)
|
||||||
}
|
}
|
||||||
.padding([.trailing], 20)
|
|
||||||
|
|
||||||
HStack(alignment: .bottom) {
|
HStack(alignment: .bottom) {
|
||||||
Text("\(bar.likes > 0 ? "\(bar.likes)" : "")")
|
Text("\(bar.likes > 0 ? "\(bar.likes)" : "")")
|
||||||
@ -90,7 +92,7 @@ struct EventActionBar: View {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.bar.likes = liked.total
|
self.bar.likes = liked.total
|
||||||
if liked.event.pubkey == our_pubkey {
|
if liked.event.pubkey == keypair.pubkey {
|
||||||
self.bar.our_like = liked.event
|
self.bar.our_like = liked.event
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,7 +101,7 @@ struct EventView: View {
|
|||||||
|
|
||||||
if has_action_bar {
|
if has_action_bar {
|
||||||
let bar = make_actionbar_model(ev: event, damus: damus)
|
let bar = make_actionbar_model(ev: event, damus: damus)
|
||||||
EventActionBar(event: event, our_pubkey: damus.pubkey, profiles: damus.profiles, bar: bar)
|
EventActionBar(event: event, keypair: damus.keypair, profiles: damus.profiles, bar: bar)
|
||||||
}
|
}
|
||||||
|
|
||||||
Divider()
|
Divider()
|
||||||
|
@ -8,17 +8,17 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct FollowButtonView: View {
|
struct FollowButtonView: View {
|
||||||
let pubkey: String
|
let target: FollowTarget
|
||||||
@State var follow_state: FollowState
|
@State var follow_state: FollowState
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Button("\(follow_btn_txt(follow_state))") {
|
Button("\(follow_btn_txt(follow_state))") {
|
||||||
follow_state = perform_follow_btn_action(follow_state, target: pubkey)
|
follow_state = perform_follow_btn_action(follow_state, target: target)
|
||||||
}
|
}
|
||||||
.buttonStyle(.bordered)
|
.buttonStyle(.bordered)
|
||||||
.onReceive(handle_notify(.followed)) { notif in
|
.onReceive(handle_notify(.followed)) { notif in
|
||||||
let pk = notif.object as! String
|
let pk = notif.object as! String
|
||||||
if pk != pubkey {
|
if pk != target.pubkey {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ struct FollowButtonView: View {
|
|||||||
}
|
}
|
||||||
.onReceive(handle_notify(.unfollowed)) { notif in
|
.onReceive(handle_notify(.unfollowed)) { notif in
|
||||||
let pk = notif.object as! String
|
let pk = notif.object as! String
|
||||||
if pk != pubkey {
|
if pk != target.pubkey {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,10 +35,43 @@ struct FollowButtonView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
struct FollowButtonPreviews: View {
|
||||||
struct FollowButtonView_Previews: PreviewProvider {
|
let target: FollowTarget = .pubkey("")
|
||||||
static var previews: some View {
|
var body: some View {
|
||||||
FollowButtonView()
|
VStack {
|
||||||
|
Text("Unfollows")
|
||||||
|
FollowButtonView(target: target, follow_state: .unfollows)
|
||||||
|
|
||||||
|
Text("Following")
|
||||||
|
FollowButtonView(target: target, follow_state: .following)
|
||||||
|
|
||||||
|
Text("Follows")
|
||||||
|
FollowButtonView(target: target, follow_state: .follows)
|
||||||
|
|
||||||
|
Text("Unfollowing")
|
||||||
|
FollowButtonView(target: target, follow_state: .unfollowing)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
struct FollowButtonView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
FollowButtonPreviews()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func perform_follow_btn_action(_ fs: FollowState, target: FollowTarget) -> FollowState {
|
||||||
|
switch fs {
|
||||||
|
case .follows:
|
||||||
|
notify(.unfollow, target)
|
||||||
|
return .following
|
||||||
|
case .following:
|
||||||
|
return .following
|
||||||
|
case .unfollowing:
|
||||||
|
return .following
|
||||||
|
case .unfollows:
|
||||||
|
notify(.follow, target)
|
||||||
|
return .unfollowing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -8,21 +8,21 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct FollowUserView: View {
|
struct FollowUserView: View {
|
||||||
let pubkey: String
|
let target: FollowTarget
|
||||||
let damus_state: DamusState
|
let damus_state: DamusState
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(alignment: .top) {
|
HStack(alignment: .top) {
|
||||||
let pmodel = ProfileModel(pubkey: pubkey, damus: damus_state)
|
let pmodel = ProfileModel(pubkey: target.pubkey, damus: damus_state)
|
||||||
let pv = ProfileView(damus_state: damus_state, profile: pmodel)
|
let pv = ProfileView(damus_state: damus_state, profile: pmodel)
|
||||||
|
|
||||||
NavigationLink(destination: pv) {
|
NavigationLink(destination: pv) {
|
||||||
ProfilePicView(pubkey: pubkey, size: PFP_SIZE, highlight: .none, image_cache: damus_state.image_cache, profiles: damus_state.profiles)
|
ProfilePicView(pubkey: target.pubkey, size: PFP_SIZE, highlight: .none, image_cache: damus_state.image_cache, profiles: damus_state.profiles)
|
||||||
}
|
}
|
||||||
|
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
let profile = damus_state.profiles.lookup(id: pubkey)
|
let profile = damus_state.profiles.lookup(id: target.pubkey)
|
||||||
ProfileName(pubkey: pubkey, profile: profile)
|
ProfileName(pubkey: target.pubkey, profile: profile)
|
||||||
if let about = profile.flatMap { $0.about } {
|
if let about = profile.flatMap { $0.about } {
|
||||||
Text(about)
|
Text(about)
|
||||||
}
|
}
|
||||||
@ -30,7 +30,7 @@ struct FollowUserView: View {
|
|||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
FollowButtonView(pubkey: pubkey, follow_state: damus_state.contacts.follow_state(pubkey))
|
FollowButtonView(target: target, follow_state: damus_state.contacts.follow_state(target.pubkey))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -43,7 +43,7 @@ struct FollowingView: View {
|
|||||||
ScrollView {
|
ScrollView {
|
||||||
LazyVStack(alignment: .leading) {
|
LazyVStack(alignment: .leading) {
|
||||||
ForEach(contact.referenced_pubkeys) { pk in
|
ForEach(contact.referenced_pubkeys) { pk in
|
||||||
FollowUserView(pubkey: pk.ref_id, damus_state: damus_state)
|
FollowUserView(target: .pubkey(pk.ref_id), damus_state: damus_state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
195
damus/Views/LoginView.swift
Normal file
195
damus/Views/LoginView.swift
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
//
|
||||||
|
// LoginView.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by William Casarin on 2022-05-22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
enum ParsedKey {
|
||||||
|
case pub(String)
|
||||||
|
case priv(String)
|
||||||
|
case hex(String)
|
||||||
|
|
||||||
|
var is_pub: Bool {
|
||||||
|
if case .pub = self {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var is_hex: Bool {
|
||||||
|
if case .hex = self {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LoginView: View {
|
||||||
|
@State var key: String = ""
|
||||||
|
@State var is_pubkey: Bool = false
|
||||||
|
@State var error: String? = nil
|
||||||
|
|
||||||
|
func get_error(parsed_key: ParsedKey?) -> String? {
|
||||||
|
if self.error != nil {
|
||||||
|
return self.error
|
||||||
|
}
|
||||||
|
|
||||||
|
if !key.isEmpty && parsed_key == nil {
|
||||||
|
return "Invalid key"
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
ZStack(alignment: .top) {
|
||||||
|
DamusGradient()
|
||||||
|
VStack {
|
||||||
|
Text("Login")
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.font(.title)
|
||||||
|
.padding()
|
||||||
|
|
||||||
|
Text("Enter your account key to login:")
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.padding()
|
||||||
|
|
||||||
|
KeyInput("nsec1...", key: $key)
|
||||||
|
|
||||||
|
let parsed = parse_key(key)
|
||||||
|
|
||||||
|
if parsed?.is_hex ?? false {
|
||||||
|
Text("This is an old-style nostr key. We're not sure if it's a pubkey or private key. Please toggle the button below if this a public key.")
|
||||||
|
.font(.subheadline.bold())
|
||||||
|
.foregroundColor(.white)
|
||||||
|
PubkeySwitch(isOn: $is_pubkey)
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
|
||||||
|
if let error = get_error(parsed_key: parsed) {
|
||||||
|
Text(error)
|
||||||
|
.foregroundColor(.red)
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
|
||||||
|
if parsed?.is_pub ?? false {
|
||||||
|
Text("This is a public key, you will not be able to make posts or interact in any way. This is used for viewing accounts from their perspective.")
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
|
||||||
|
if let p = parsed {
|
||||||
|
DamusWhiteButton("Login") {
|
||||||
|
if !process_login(p, is_pubkey: self.is_pubkey) {
|
||||||
|
self.error = "Invalid key"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
.navigationBarBackButtonHidden(true)
|
||||||
|
.navigationBarItems(leading: BackNav())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PubkeySwitch: View {
|
||||||
|
@Binding var isOn: Bool
|
||||||
|
var body: some View {
|
||||||
|
HStack {
|
||||||
|
Toggle(isOn: $isOn) {
|
||||||
|
Text("Public Key?")
|
||||||
|
.foregroundColor(.white)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parse_key(_ thekey: String) -> ParsedKey? {
|
||||||
|
var key = thekey
|
||||||
|
if key.count > 0 && key.first! == "@" {
|
||||||
|
key = String(key.dropFirst())
|
||||||
|
}
|
||||||
|
if hex_decode(key) != nil {
|
||||||
|
return .hex(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let bech_key = decode_bech32_key(key) {
|
||||||
|
switch bech_key {
|
||||||
|
case .pub(let pk):
|
||||||
|
return .pub(pk)
|
||||||
|
case .sec(let sec):
|
||||||
|
return .priv(sec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func process_login(_ key: ParsedKey, is_pubkey: Bool) -> Bool {
|
||||||
|
switch key {
|
||||||
|
case .priv(let priv):
|
||||||
|
save_privkey(privkey: priv)
|
||||||
|
guard let pk = privkey_to_pubkey(privkey: priv) else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
save_pubkey(pubkey: pk)
|
||||||
|
|
||||||
|
case .pub(let pub):
|
||||||
|
clear_privkey()
|
||||||
|
save_pubkey(pubkey: pub)
|
||||||
|
|
||||||
|
case .hex(let hexstr):
|
||||||
|
if is_pubkey {
|
||||||
|
clear_privkey()
|
||||||
|
save_pubkey(pubkey: hexstr)
|
||||||
|
} else {
|
||||||
|
save_privkey(privkey: hexstr)
|
||||||
|
guard let pk = privkey_to_pubkey(privkey: hexstr) else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
save_pubkey(pubkey: pk)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notify(.login, ())
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
struct KeyInput: View {
|
||||||
|
let title: String
|
||||||
|
let key: Binding<String>
|
||||||
|
|
||||||
|
init(_ title: String, key: Binding<String>) {
|
||||||
|
self.title = title
|
||||||
|
self.key = key
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
TextField("", text: key)
|
||||||
|
.placeholder(when: key.wrappedValue.isEmpty) {
|
||||||
|
Text(title).foregroundColor(.white.opacity(0.6))
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
.background {
|
||||||
|
RoundedRectangle(cornerRadius: 4.0).opacity(0.2)
|
||||||
|
}
|
||||||
|
.autocapitalization(.none)
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.font(.body.monospaced())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LoginView_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
let pubkey = "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"
|
||||||
|
let bech32_pubkey = "KeyInput"
|
||||||
|
Group {
|
||||||
|
LoginView(key: pubkey)
|
||||||
|
LoginView(key: bech32_pubkey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -10,7 +10,6 @@ import SwiftUI
|
|||||||
enum Timeline: String, CustomStringConvertible {
|
enum Timeline: String, CustomStringConvertible {
|
||||||
case home
|
case home
|
||||||
case notifications
|
case notifications
|
||||||
case global
|
|
||||||
case search
|
case search
|
||||||
|
|
||||||
var description: String {
|
var description: String {
|
||||||
@ -86,7 +85,6 @@ struct TabBar: View {
|
|||||||
TabButton(timeline: .home, img: "house", selected: $selected, action: action)
|
TabButton(timeline: .home, img: "house", selected: $selected, action: action)
|
||||||
TabButton(timeline: .search, img: "magnifyingglass.circle", selected: $selected, action: action)
|
TabButton(timeline: .search, img: "magnifyingglass.circle", selected: $selected, action: action)
|
||||||
NotificationsTab(new_notifications: $new_notifications, selected: $selected, action: action)
|
NotificationsTab(new_notifications: $new_notifications, selected: $selected, action: action)
|
||||||
TabButton(timeline: .global, img: "globe.americas", selected: $selected, action: action)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,21 +45,6 @@ func follow_btn_enabled_state(_ fs: FollowState) -> Bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func perform_follow_btn_action(_ fs: FollowState, target: String) -> FollowState {
|
|
||||||
switch fs {
|
|
||||||
case .follows:
|
|
||||||
notify(.unfollow, target)
|
|
||||||
return .following
|
|
||||||
case .following:
|
|
||||||
return .following
|
|
||||||
case .unfollowing:
|
|
||||||
return .following
|
|
||||||
case .unfollows:
|
|
||||||
notify(.follow, target)
|
|
||||||
return .unfollowing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ProfileView: View {
|
struct ProfileView: View {
|
||||||
let damus_state: DamusState
|
let damus_state: DamusState
|
||||||
|
|
||||||
@ -76,7 +61,7 @@ struct ProfileView: View {
|
|||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
FollowButtonView(pubkey: profile.pubkey, follow_state: damus_state.contacts.follow_state(profile.pubkey))
|
FollowButtonView(target: profile.get_follow_target(), follow_state: damus_state.contacts.follow_state(profile.pubkey))
|
||||||
}
|
}
|
||||||
|
|
||||||
if let pubkey = profile.pubkey {
|
if let pubkey = profile.pubkey {
|
||||||
|
@ -39,20 +39,22 @@ struct SaveKeysView: View {
|
|||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
.padding(.bottom, 10)
|
.padding(.bottom, 10)
|
||||||
|
|
||||||
SaveKeyView(text: account.pubkey, is_copied: $pub_copied)
|
SaveKeyView(text: account.pubkey_bech32, is_copied: $pub_copied)
|
||||||
.padding(.bottom, 10)
|
.padding(.bottom, 10)
|
||||||
|
|
||||||
Text("Private Key")
|
if pub_copied {
|
||||||
.font(.title2.bold())
|
Text("Private Key")
|
||||||
.foregroundColor(.white)
|
.font(.title2.bold())
|
||||||
.padding(.bottom, 10)
|
.foregroundColor(.white)
|
||||||
|
.padding(.bottom, 10)
|
||||||
Text("This is your secret account key. You need this to access your account. Don't share this with anyone! Save it in a password manager and keep it safe!")
|
|
||||||
.foregroundColor(.white)
|
Text("This is your secret account key. You need this to access your account. Don't share this with anyone! Save it in a password manager and keep it safe!")
|
||||||
.padding(.bottom, 10)
|
.foregroundColor(.white)
|
||||||
|
.padding(.bottom, 10)
|
||||||
SaveKeyView(text: account.privkey, is_copied: $priv_copied)
|
|
||||||
.padding(.bottom, 10)
|
SaveKeyView(text: account.privkey_bech32, is_copied: $priv_copied)
|
||||||
|
.padding(.bottom, 10)
|
||||||
|
}
|
||||||
|
|
||||||
if pub_copied && priv_copied {
|
if pub_copied && priv_copied {
|
||||||
if loading {
|
if loading {
|
||||||
@ -73,6 +75,8 @@ struct SaveKeysView: View {
|
|||||||
}
|
}
|
||||||
.padding(20)
|
.padding(20)
|
||||||
}
|
}
|
||||||
|
.navigationBarBackButtonHidden(true)
|
||||||
|
.navigationBarItems(leading: BackNav())
|
||||||
}
|
}
|
||||||
|
|
||||||
func complete_account_creation(_ account: CreateAccountModel) {
|
func complete_account_creation(_ account: CreateAccountModel) {
|
||||||
@ -90,11 +94,15 @@ struct SaveKeysView: View {
|
|||||||
switch wsev {
|
switch wsev {
|
||||||
case .connected:
|
case .connected:
|
||||||
let metadata = create_account_to_metadata(account)
|
let metadata = create_account_to_metadata(account)
|
||||||
let metadata_ev = make_metadata_event(keypair: account.keypair, metadata: metadata)
|
let m_metadata_ev = make_metadata_event(keypair: account.keypair, metadata: metadata)
|
||||||
let contacts_ev = make_first_contact_event(keypair: account.keypair)
|
let m_contacts_ev = make_first_contact_event(keypair: account.keypair)
|
||||||
|
|
||||||
self.pool.send(.event(metadata_ev))
|
if let metadata_ev = m_metadata_ev {
|
||||||
self.pool.send(.event(contacts_ev))
|
self.pool.send(.event(metadata_ev))
|
||||||
|
}
|
||||||
|
if let contacts_ev = m_contacts_ev {
|
||||||
|
self.pool.send(.event(contacts_ev))
|
||||||
|
}
|
||||||
|
|
||||||
save_keypair(pubkey: account.pubkey, privkey: account.privkey)
|
save_keypair(pubkey: account.pubkey, privkey: account.privkey)
|
||||||
notify(.login, account.keypair)
|
notify(.login, account.keypair)
|
||||||
|
@ -45,6 +45,9 @@ struct SetupView: View {
|
|||||||
NavigationLink(destination: CreateAccountView(), tag: .create_account, selection: $state ) {
|
NavigationLink(destination: CreateAccountView(), tag: .create_account, selection: $state ) {
|
||||||
EmptyView()
|
EmptyView()
|
||||||
}
|
}
|
||||||
|
NavigationLink(destination: LoginView(), tag: .login, selection: $state ) {
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
|
||||||
Image("logo-nobg")
|
Image("logo-nobg")
|
||||||
.resizable()
|
.resizable()
|
||||||
@ -64,7 +67,7 @@ struct SetupView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Button("Login") {
|
Button("Login") {
|
||||||
notify(.login, ())
|
self.state = .login
|
||||||
}
|
}
|
||||||
.padding([.top, .bottom], 20)
|
.padding([.top, .bottom], 20)
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
|
@ -26,7 +26,7 @@ struct MainView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
Group {
|
Group {
|
||||||
if let kp = keypair, !needs_setup {
|
if let kp = keypair, !needs_setup {
|
||||||
ContentView(pubkey: kp.pubkey, privkey: kp.privkey)
|
ContentView(keypair: kp)
|
||||||
} else {
|
} else {
|
||||||
SetupView()
|
SetupView()
|
||||||
.onReceive(handle_notify(.login)) { notif in
|
.onReceive(handle_notify(.login)) { notif in
|
||||||
|
51
damusTests/Bech32Tests.swift
Normal file
51
damusTests/Bech32Tests.swift
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
//
|
||||||
|
// Bech32Tests.swift
|
||||||
|
// damusTests
|
||||||
|
//
|
||||||
|
// Created by William Casarin on 2022-05-22.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
@testable import damus
|
||||||
|
|
||||||
|
class Bech32Tests: XCTestCase {
|
||||||
|
|
||||||
|
override func setUpWithError() throws {
|
||||||
|
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tearDownWithError() throws {
|
||||||
|
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_bech32_encode_decode() throws {
|
||||||
|
// This is an example of a functional test case.
|
||||||
|
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
||||||
|
// Any test you write for XCTest can be annotated as throws and async.
|
||||||
|
// Mark your test throws to produce an unexpected failure when your test encounters an uncaught error.
|
||||||
|
// Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards.
|
||||||
|
|
||||||
|
let pubkey = "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"
|
||||||
|
guard let b32_pubkey = bech32_pubkey(pubkey) else {
|
||||||
|
XCTAssert(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let decoded = try? bech32_decode(b32_pubkey) else {
|
||||||
|
XCTAssert(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let encoded = hex_encode(decoded.data)
|
||||||
|
|
||||||
|
XCTAssertEqual(encoded, pubkey)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testPerformanceExample() throws {
|
||||||
|
// This is an example of a performance test case.
|
||||||
|
self.measure {
|
||||||
|
// Put the code you want to measure the time of here.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user