Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New feature: Polls #243

Closed
wants to merge 37 commits into from
Closed
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
ebf80e2
ln address simplified
0xtlt Dec 23, 2022
74cf389
Https picture
0xtlt Dec 23, 2022
7d85954
WEBP
0xtlt Dec 23, 2022
8263ac6
Merge branch 'master' of https://github.com/0xtlt/damus
0xtlt Dec 23, 2022
08cde75
Merge branch 'damus-io:master' into master
0xtlt Dec 24, 2022
fef8455
Merge branch 'damus-io:master' into master
0xtlt Dec 28, 2022
45a3747
Merge branch 'damus-io:master' into master
0xtlt Dec 31, 2022
c9844ba
Merge branch 'damus-io:master' into master
0xtlt Jan 2, 2023
2be0b2c
Merge branch 'damus-io:master' into master
0xtlt Jan 4, 2023
80ed2bc
Polls
0xtlt Jan 4, 2023
e4c7752
Update PostView.swift
0xtlt Jan 4, 2023
20963d4
Update PostView.swift
0xtlt Jan 4, 2023
da2bce1
create Polls
0xtlt Jan 4, 2023
d217420
Display without results
0xtlt Jan 4, 2023
4c631a9
make choice
0xtlt Jan 4, 2023
aaa9292
Update EventView.swift
0xtlt Jan 4, 2023
c5ed601
Update EventView.swift
0xtlt Jan 4, 2023
1315154
Fix likes count
0xtlt Jan 4, 2023
3e71465
Update EventView.swift
0xtlt Jan 4, 2023
6023efa
Merge branch 'damus-io:master' into master
0xtlt Jan 4, 2023
47a62a6
Config
0xtlt Jan 4, 2023
7c9d6f3
Update EventView.swift
0xtlt Jan 4, 2023
ab88b91
Count fixed
0xtlt Jan 4, 2023
fb9ced4
Optimization
0xtlt Jan 4, 2023
1b9ae2d
Changes and fixes
0xtlt Jan 4, 2023
3811f71
Update EventView.swift
0xtlt Jan 4, 2023
e99fab5
Merge branch 'damus-io:master' into master
0xtlt Jan 6, 2023
8fdd190
Merge branch 'master' into polls
0xtlt Jan 6, 2023
8a8b897
NIP 41
0xtlt Jan 6, 2023
6fc9c8d
Merge branch 'damus-io:master' into master
0xtlt Jan 6, 2023
c10cb8f
Merge branch 'master' into polls
0xtlt Jan 6, 2023
d8df64b
Relay fixed
0xtlt Jan 6, 2023
34602dd
Merge branch 'master' into polls
0xtlt Jan 9, 2023
17cf609
Merge branch 'damus-io:master' into master
0xtlt Jan 11, 2023
f11798d
Merge branch 'master' into polls
0xtlt Jan 11, 2023
c3d26dc
guards
0xtlt Jan 11, 2023
b07629b
Update EventView.swift
0xtlt Jan 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions damus/Models/HomeModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,12 @@ class HomeModel: ObservableObject {
}

// CHECK SIGS ON THESE

let likes_content = ["👍", "+", "🤙"]

if !likes_content.contains(ev.content) {
return
}
Comment on lines +181 to +185
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this should be removed?


switch damus_state.likes.add_event(ev, target: e.ref_id) {
case .already_counted:
Expand Down
3 changes: 2 additions & 1 deletion damus/Models/Mentions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,8 @@ func make_post_tags(post_blocks: [PostBlock], tags: [[String]]) -> PostTags {
}

func post_to_event(post: NostrPost, privkey: String, pubkey: String) -> NostrEvent {
let tags = post.references.map(refid_to_tag)
var tags = post.references.map(refid_to_tag)
tags.append(contentsOf: post.tags)
let post_blocks = parse_post_blocks(content: post.content)
let post_tags = make_post_tags(post_blocks: post_blocks, tags: tags)
let content = render_blocks(blocks: post_tags.blocks)
Expand Down
8 changes: 6 additions & 2 deletions damus/Models/Post.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,20 @@ struct NostrPost {
let content: String
let references: [ReferencedId]

init (content: String, references: [ReferencedId]) {
let tags: [[String]]

init (content: String, references: [ReferencedId], tags: [[String]] = []) {
self.content = content
self.references = references
self.kind = .text
self.tags = tags
}

init (content: String, references: [ReferencedId], kind: NostrKind) {
init (content: String, references: [ReferencedId], kind: NostrKind, tags: [[String]] = []) {
self.content = content
self.references = references
self.kind = kind
self.tags = tags
}
}

Expand Down
9 changes: 9 additions & 0 deletions damus/Nostr/NostrEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,15 @@ func make_like_event(pubkey: String, privkey: String, liked: NostrEvent) -> Nost
return ev
}

func make_poll_choice_event(pubkey: String, privkey: String, event: NostrEvent, choice_index: Int) -> NostrEvent {
var tags: [[String]] = [["e", event.id]]
let ev = NostrEvent(content: "\(choice_index)", pubkey: pubkey, kind: 8, tags: tags)
ev.calculate_id()
ev.sign(privkey: privkey)

return ev
}

func gather_reply_ids(our_pubkey: String, from: NostrEvent) -> [ReferencedId] {
var ids = get_referenced_ids(tags: from.tags, key: "e").first.map { [$0] } ?? []

Expand Down
8 changes: 8 additions & 0 deletions damus/Views/ConfigView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ struct ConfigView: View {
@State var relays: [RelayDescriptor]
@EnvironmentObject var user_settings: UserSettingsStore

/// True = poll will display votes from everyone, false = poll will display votes from friends only (and friends of friends)
/// This option is included because of the potential problem with bots.
@AppStorage("poll_results_everyone") var poll_results_everyone: Bool = false
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've never used AppStorage before. Does this just save it in UserSettings? I eventually want to scope user settings by pubkey so maybe its best to have it in there


let generator = UIImpactFeedbackGenerator(style: .light)

init(state: DamusState) {
Expand Down Expand Up @@ -103,6 +107,10 @@ struct ConfigView: View {
}
}

Section("Poll feature") {
Toggle("See everyone votes", isOn: $poll_results_everyone)
}

Section("Wallet Selector") {
Toggle("Show wallet selector", isOn: $user_settings.show_wallet_selector).toggleStyle(.switch)
Picker("Select default wallet",
Expand Down
187 changes: 187 additions & 0 deletions damus/Views/EventView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,12 @@ struct BuilderEventView: View {
}
}

enum PollResultsDisplay {
case none
case friends
case everyone
}

struct EventView: View {
let event: NostrEvent
let highlight: Highlight
Expand All @@ -131,6 +137,14 @@ struct EventView: View {
let size: EventViewKind

@EnvironmentObject var action_bar: ActionBarModel

@State var show_poll_results: Bool = false
@State var subscription_poll_uuid: String = UUID().description

// String = user pubkey and int is the choice
@State var choices: [(String, Int)] = []

@AppStorage("poll_results_everyone") var poll_results_everyone: Bool = false

init(event: NostrEvent, highlight: Highlight, has_action_bar: Bool, damus: DamusState, show_friend_icon: Bool, size: EventViewKind = .normal) {
self.event = event
Expand Down Expand Up @@ -161,6 +175,56 @@ struct EventView: View {
self.show_friend_icon = show_friend_icon
self.size = size
}

func handle_event(relay_id: String, ev: NostrConnectionEvent) {
guard case .nostr_event(let nostr_response) = ev else {
return
}

guard case .event(let id, let nostr_event) = nostr_response else {
return
}

// Is current event
if id == subscription_poll_uuid {
if nostr_event.kind != 8 {
return
}

// If the pubkey is our, display the results
if nostr_event.pubkey == damus.pubkey {
show_poll_results = true
}

// Check if the choice is already submitted by the pubkey
if choices.contains(where: { $0.0 == nostr_event.pubkey }) {
return
}

// Check the choice
guard let choice_int = Int(nostr_event.content) else {
return
}

choices.append((nostr_event.pubkey, choice_int))
}
}

func unsubscribe_poll() {
damus.pool.unsubscribe(sub_id: subscription_poll_uuid)
}

func subscribe_poll() {
let filters: [NostrFilter] = [
NostrFilter(
kinds: [8],
referenced_ids: [event.id]
)
]

damus.pool.register_handler(sub_id: subscription_poll_uuid, handler: handle_event)
damus.pool.send(.subscribe(.init(filters: filters, sub_id: subscription_poll_uuid)))
}

var body: some View {
return Group {
Expand Down Expand Up @@ -198,6 +262,12 @@ struct EventView: View {
func TextEvent(_ event: NostrEvent, pubkey: String) -> some View {
let content = event.get_content(damus.keypair.privkey)

let poll_choices = event.tags.filter { value in
return value.count >= 2 && value[0] == "poll" && !value[1].isEmpty
}.prefix(4).map { value in
return value[1]
}

return HStack(alignment: .top) {
let profile = damus.profiles.lookup(id: pubkey)

Expand Down Expand Up @@ -247,6 +317,99 @@ struct EventView: View {
NoteContentView(privkey: damus.keypair.privkey, event: event, profiles: damus.profiles, previews: damus.previews, show_images: should_show_img, artifacts: .just_content(content), size: self.size)
.frame(maxWidth: .infinity, alignment: .leading)

// MARK: - Poll
if poll_choices.count >= 2 {
let filtered_choices = choices.filter({ poll_results_everyone ? true : damus.contacts.is_in_friendosphere($0.0) })
let total_count = filtered_choices.count == 0 ? 1 : filtered_choices.count

VStack(alignment: .leading, spacing: 15) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be its own view

ForEach(0 ..< poll_choices.count, id: \.self) { index in
let this_choice_count = filtered_choices.filter({ $0.1 == index }).count
let percent = CGFloat(this_choice_count) / CGFloat(total_count)

HStack {
if show_poll_results {
/// Placeholder text for the size
Text("100%")
.font(.caption)
.opacity(0)
.overlay {
Text("\(Int(floor(percent * 100)))%")
.font(.caption)
}
}

HStack {
Button {
if !show_poll_results {
send_poll_choice(index)
show_poll_results = true
}
} label: {
Text(poll_choices[index])
.font(.caption)
.frame(minWidth: 0, maxWidth: .infinity)
.padding(12)
}
}
.background(alignment: .leading) {
GeometryReader { geometry in
withAnimation {
Rectangle()
.foregroundColor(.accentColor.opacity(0.2))
.frame(
minWidth: 0,
maxWidth: show_poll_results
? percent * geometry.size.width
: 0
)
}
}
}
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.gray.opacity(0.4), lineWidth: 2)
)
.cornerRadius(10)
}
}

if !show_poll_results {
Button {
show_poll_results = true
} label: {
Label("Show result", systemImage: "number.circle")
.font(.caption)
.frame(minWidth: 0, maxWidth: .infinity)
.padding(12)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.gray.opacity(0.4), lineWidth: 2)
)
.cornerRadius(10)
}
}

Text("\(filtered_choices.count) vote\(filtered_choices.count >= 2 ? "s" : "")\(poll_results_everyone ? "" : " - close circle of friends votes only")")
.font(.footnote)
.foregroundColor(.gray)
}
.frame(minWidth: 0, maxWidth: .infinity)
.padding()
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.gray.opacity(0.4), lineWidth: 2)
)
.cornerRadius(10)
.onAppear {
subscribe_poll()
}
.onDisappear {
unsubscribe_poll()
}
}

// MARK: - Action bar
if has_action_bar {
if size == .selected {
Text("\(format_date(event.created_at))")
Expand Down Expand Up @@ -276,6 +439,16 @@ struct EventView: View {
.padding([.bottom], 2)
.event_context_menu(event, pubkey: pubkey, privkey: damus.keypair.privkey)
}

func send_poll_choice(_ choice: Int) {
guard let privkey = damus.keypair.privkey else {
return
}

let choice_ev = make_poll_choice_event(pubkey: damus.pubkey, privkey: privkey, event: event, choice_index: choice)

damus.pool.send(.event(choice_ev))
}
}

// blame the porn bots for this code
Expand Down Expand Up @@ -411,6 +584,20 @@ func make_actionbar_model(ev: NostrEvent, damus: DamusState) -> ActionBarModel {

struct EventView_Previews: PreviewProvider {
static var previews: some View {
EventView(
event: NostrEvent(
content: "jb55.com/red-me.jb55 cool",
pubkey: "pk",
tags: [["random", "kind"], ["poll", "yes"], ["poll", "no"]],
createdAt: Int64(Date().timeIntervalSince1970 - 100)
),
highlight: .none,
has_action_bar: true,
damus: test_damus_state(),
show_friend_icon: true,
size: .selected
)

VStack {
EventView(damus: test_damus_state(), event: NostrEvent(content: "hello there https://jb55.com/s/Oct12-150217.png https://jb55.com/red-me.jb55 cool", pubkey: "pk"), show_friend_icon: true, size: .small)
EventView(damus: test_damus_state(), event: NostrEvent(content: "hello there https://jb55.com/s/Oct12-150217.png https://jb55.com/red-me.jb55 cool", pubkey: "pk"), show_friend_icon: true, size: .normal)
Expand Down
Loading