damus-io / damus

iOS nostr client
GNU General Public License v3.0
2k stars 289 forks source link

Scheduled and draft notes [500,000 sats bounty] #1241

Open alltheseas opened 1 year ago

alltheseas commented 1 year ago

user story

As a Damus user who creates posts, I would like to schedule a note to be sent at a particular time, so that I can post at a specific time in the future without having to remember and post at that time.

acceptance criteria

  1. User has post later functionality
  2. Post later functionality includes a way to specify a specific time to post - e.g. year, month, day, HH:mm
  3. User can see scheduled post queue
  4. User can remove scheduled posts
  5. User has a "drafts" folder of unscheduled posts (see https://github.com/damus-io/damus/issues/596#issuecomment-1456430333)
  6. Drafts not lost upon app close
  7. Bonus: implements drafts NIP
alltheseas commented 11 months ago

300,000 sats bounty posted by geeknik

https://damus.io/note1f75nu0ecexmapwcm8alxwhhud5pfcseztj5l83n4gne67drjesrqqyeyvt

alltheseas commented 11 months ago

Maybe this is part of Damus purple

bcss9000 commented 11 months ago

Brainstorm based upon my limited understanding of https://github.com/damus-io/damus/blob/master/damus/Views/PostView.swift. Probably totally wrong, but maybe it gives someone a headstart on collecting my 300K bounty. Yes, I'm doxxing one of my super secret GitHub accounts. :smile:

struct ScheduledPost: Identifiable {
    var id = UUID()
    var content: String
    var postTime: Date
    var isPosted: Bool = false
}

Extend DamusState to manage scheduled posts?


class DamusState: ObservableObject {
    @Published var scheduledPosts: [ScheduledPost] = []
    // ... existing code
    func schedulePost(_ post: ScheduledPost) {
        scheduledPosts.append(post)
    }
    func removeScheduledPost(_ post: ScheduledPost) {
        scheduledPosts.removeAll { $0.id == post.id }
    }
    // ... existing code
}

Modifications in PostView?

struct PostView: View {
    @ObservedObject var damusState: DamusState
    @State private var isScheduling: Bool = false
    @State private var scheduledDate: Date = Date()
    // ... existing code

    var body: some View {
        // ... existing code
        Toggle("Schedule Post", isOn: $isScheduling)
        if isScheduling {
            DatePicker("Scheduled Time", selection: $scheduledDate, displayedComponents: [.date, .hourAndMinute])
        }
        // ... existing code
    }
}

Create a view for scheduled post queue?

struct ScheduledPostsView: View {
    @ObservedObject var damusState: DamusState

    var body: some View {
        List {
            ForEach(damusState.scheduledPosts) { post in
                HStack {
                    Text(post.content)
                    Button(action: {
                        damusState.removeScheduledPost(post)
                    }) {
                        Text("Remove")
                    }
                }
            }
        }
    }
}

Test Scheduling Post

func testSchedulePost() throws {
    // Setup
    let postContent = "Test Post"
    let scheduledTime = Date().addingTimeInterval(60)  // 1 minute from now
    let scheduledPost = ScheduledPost(postContent: postContent, scheduledTime: scheduledTime, isPosted: false)

    // Action
    damusState.schedulePost(scheduledPost)

    // Assertion
    XCTAssertEqual(damusState.scheduledPosts.count, 1)
    XCTAssertEqual(damusState.scheduledPosts[0].postContent, postContent)
    XCTAssertEqual(damusState.scheduledPosts[0].scheduledTime, scheduledTime)
    XCTAssertEqual(damusState.scheduledPosts[0].isPosted, false)
}

Test Post Publishing

func testPostPublishing() throws {
    // Setup
    // ... similar to above

    // Simulate time passage
    // ... your logic to move time forward

    // Action
    // ... your logic to trigger post publishing check

    // Assertion
    XCTAssertEqual(damusState.scheduledPosts[0].isPosted, true)
}

Test Viewing Scheduled Posts

func testViewScheduledPosts() throws {
    // Setup
    // ... similar to above

    // Action
    let scheduledPostsView = ScheduledPostsView(damusState: damusState)

    // Assertion
    // ... your logic to check the view displays correctly
}
jb55 commented 11 months ago

scheduled notes are not possible on a client. this would have to be a third party service.

alltheseas commented 11 months ago

scheduled notes are not possible on a client. this would have to be a third party service.

What's the limitation client side?

jb55 commented 11 months ago

On Mon, Oct 23, 2023 at 05:48:30PM -0700, alltheseas wrote:

scheduled notes are not possible on a client. this would have to be a third party service.

What's the limitation client side?

scheduled post implies it gets sent when you don't have the app open.

geeknik commented 11 months ago

We can cheat that. 🤙🏻

Use the UserNotifications framework to schedule local notifications. These notifications can be set to trigger at the time when the post is supposed to be sent. When the notification triggers, it can execute the task of sending the post.

Add'l information: https://developer.apple.com/documentation/uikit/app_and_environment/scenes/preparing_your_ui_to_run_in_the_background/using_background_tasks_to_update_your_app

I believe. 😎

jb55 commented 11 months ago

On Tue, Oct 24, 2023 at 05:47:45AM -0700, geeknik wrote:

We can cheat that. 🤙🏻

Use the UserNotifications framework to schedule local notifications. These notifications can be set to trigger at the time when the post is supposed to be sent. When the notification triggers, it can execute the task of sending the post.

Add'l information: https://developer.apple.com/documentation/uikit/app_and_environment/scenes/preparing_your_ui_to_run_in_the_background/using_background_tasks_to_update_your_app

I believe. 😎

Interesting idea! I didn't know you could do that.

alltheseas commented 11 months ago

Design suggestions cc @robagreda https://damus.io/note1yk5kgeu64c7xuy5umjt7q60j5dy4rmnakt8mc99zvw4d8fp7cknqlzekcm

alltheseas commented 10 months ago

We can cheat that. 🤙🏻

Use the UserNotifications framework to schedule local notifications. These notifications can be set to trigger at the time when the post is supposed to be sent. When the notification triggers, it can execute the task of sending the post.

Add'l information: https://developer.apple.com/documentation/uikit/app_and_environment/scenes/preparing_your_ui_to_run_in_the_background/using_background_tasks_to_update_your_app

I believe. 😎

@geeknik do you know if phone needs to be on, and Damus app needs to be running in the background to run this feature using your proposed implementation?

geeknik commented 10 months ago

The phone would need to be on. However, when a local notification is scheduled, the system takes care of delivering the notification at the right time; the app does not need to be running for this to happen.[1][2]

This means that the app does not need to be running in the background for the notification to trigger. However, if the app is completely terminated (either by the system or by the user), the ability to handle actions from notifications may be limited.[3]

In terms of executing tasks in response to a notification, the app can perform a background update task in response to a remote notification, but this requires the app to be running in the background.[4]

If the app has been force-quit by the user, the system does not launch the app to perform background tasks or deliver notifications, until the user opens the app again.[5]

  1. https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/SchedulingandHandlingLocalNotifications.html

  2. https://developer.apple.com/documentation/usernotifications

  3. https://stackoverflow.com/questions/55411137/handle-push-notification-when-app-is-in-background-or-running-state-not-when-app

  4. https://developer.apple.com/documentation/usernotifications/implementing_background_push_notifications

  5. https://developer.apple.com/videos/play/wwdc2019/707/

alltheseas commented 10 months ago

@danieldaquino see above related to today's discussion 🙏

jb55 commented 10 months ago

This is something that nip-26-delegation would be particularly good at. I'm not even sure we can do networking stuff in a push notification. We would also still run into issues if the phone was dead.

alltheseas commented 9 months ago

Added additional acceptance criteria of "drafts folder".

Increasing bounty by 100,000 sats to 400,000 sats by dimi

https://damus.io/note1mqkelkceatgerfg4teaz8tprjm65m6ar84vrufq99w8mkc2070fqk7hj93

alltheseas commented 6 months ago

Plebianon pledged +100k sats

https://damus.io/nevent1qqsteaacmt9ag8s0h99shl90p25wtned4453um5r6ce5eat0gc0edcgpzpmhxue69uhkummnw3ezuamfdejsz9rhwden5te0wfjkccte9ejxzmt4wvhxjmcpz4mhxue69uhk2er9dchxummnw3ezumrpdejqzrthwden5te0dehhxtnvdakq8ny7lx

alltheseas commented 6 months ago

Draft NIP https://github.com/nostr-protocol/nips/pull/1124

jb55 commented 6 months ago

On Tue, Apr 02, 2024 at 07:41:35AM -0700, alltheseas wrote:

Draft NIP https://github.com/nostr-protocol/nips/pull/1124

this has nothing to do with scheduled notes.

alltheseas commented 6 months ago

On Tue, Apr 02, 2024 at 07:41:35AM -0700, alltheseas wrote:

Draft NIP https://github.com/nostr-protocol/nips/pull/1124

this has nothing to do with scheduled notes.

Does this NIP fit better here @jb55

https://github.com/damus-io/damus/issues/596#issuecomment-1456430333

jb55 commented 6 months ago

yes Vitor's draft spec is good for storing drafts in nostrdb and external relays