Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
Empty file added .beads/interactions.jsonl
Empty file.
38 changes: 38 additions & 0 deletions .beads/issues.jsonl

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions .beads/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"database": "beads.db",
"jsonl_export": "issues.jsonl"
}
Comment on lines +1 to +4

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if beads.db exists in repo
echo "=== Checking if beads.db exists in repo ==="
fd 'beads.db' 2>/dev/null || echo "beads.db not found"

echo ""
echo "=== Checking .gitignore for beads patterns ==="
if [ -f .gitignore ]; then
  rg -n 'beads' .gitignore || echo "No beads pattern found in .gitignore"
else
  echo ".gitignore file not found"
fi

echo ""
echo "=== Full .gitignore content ==="
cat .gitignore 2>/dev/null || echo ".gitignore not accessible"

Repository: damus-io/damus

Length of output: 357


Add beads.db to .gitignore.

The .beads/metadata.json file references beads.db, which is a SQLite database that should not be committed to version control. While beads.db doesn't currently exist in the repository, it's not excluded in .gitignore. Add a pattern to prevent accidental commits if the beads tool generates this database during development.

🤖 Prompt for AI Agents
In @.beads/metadata.json around lines 1 - 4, Add a gitignore entry to prevent
committing the SQLite DB referenced in .beads/metadata.json by updating the
repository .gitignore to ignore beads.db (and preferably any database files
under the .beads directory); add either "beads.db" and/or ".beads/beads.db" (or
a broader pattern like ".beads/*.db") to .gitignore, save, and commit the change
so the generated SQLite file cannot be accidentally checked in.

3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ TODO.bak
tags
build-git-hash.txt
.build
.beads/*.db
.beads/*.db-shm
.beads/*.db-wal
28 changes: 27 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,30 @@ Damus is an iOS client built around a local relay model ([damus-io/damus#3204](h
7. Review and follow `pull_request_template.md` when creating PRs for iOS Damus.
8. Ensure nevernesting: favor early returns and guard clauses over deeply nested conditionals; simplify control flow by exiting early instead of wrapping logic in multiple layers of `if` statements.
9. Before proposing changes, please **review and analyze if a change or upgrade to nostrdb** is beneficial to the change at hand.
10. **Never block the main thread**: All network requests, database queries, and expensive computations must run on background threads/queues. Use `Task { }`, `DispatchQueue.global()`, or Swift concurrency (`async/await`) appropriately. UI updates must dispatch back to `@MainActor`. Test for hangs and freezes before submitting.
10. **Never block the main thread**: All network requests, database queries, and expensive computations must run on background threads/queues. Use `Task { }`, `DispatchQueue.global()`, or Swift concurrency (`async/await`) appropriately. UI updates must dispatch back to `@MainActor`. Test for hangs and freezes before submitting.

## Landing the Plane (Session Completion)

**When ending a work session**, you MUST complete ALL steps below. Work is NOT complete until `git push` succeeds.

**MANDATORY WORKFLOW:**

1. **File issues for remaining work** - Create issues for anything that needs follow-up
2. **Run quality gates** (if code changed) - Tests, linters, builds
3. **Update issue status** - Close finished work, update in-progress items
4. **PUSH TO REMOTE** - This is MANDATORY:
```bash
git pull --rebase
bd sync
git push
git status # MUST show "up to date with origin"
```
5. **Clean up** - Clear stashes, prune remote branches
6. **Verify** - All changes committed AND pushed
7. **Hand off** - Provide context for next session

**CRITICAL RULES:**
- Work is NOT complete until `git push` succeeds
- NEVER stop before pushing - that leaves work stranded locally
- NEVER say "ready to push when you are" - YOU must push
- If push fails, resolve and retry until it succeeds
149 changes: 129 additions & 20 deletions damus.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

26 changes: 26 additions & 0 deletions damus/Assets.xcassets/iconography/vine.fill.imageset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"images": [
{
"filename": "vine.fill.svg",
"idiom": "universal",
"scale": "1x"
},
{
"idiom": "universal",
"scale": "2x",
"filename": "vine.fill.svg"
},
{
"idiom": "universal",
"scale": "3x",
"filename": "vine.fill.svg"
}
],
"info": {
"author": "xcode",
"version": 1
},
"properties": {
"template-rendering-intent": "template"
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 26 additions & 0 deletions damus/Assets.xcassets/iconography/vine.imageset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"images": [
{
"filename": "vine.svg",
"idiom": "universal",
"scale": "1x"
},
{
"idiom": "universal",
"scale": "2x",
"filename": "vine.svg"
},
{
"idiom": "universal",
"scale": "3x",
"filename": "vine.svg"
}
],
"info": {
"author": "xcode",
"version": 1
},
"properties": {
"template-rendering-intent": "template"
}
}
4 changes: 4 additions & 0 deletions damus/Assets.xcassets/iconography/vine.imageset/vine.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
36 changes: 30 additions & 6 deletions damus/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,8 @@ struct ContentView: View {
}

func MainContent(damus: DamusState) -> some View {
VStack {
let immersiveTimeline = selected_timeline == .home || selected_timeline == .vines
return VStack {
switch selected_timeline {
case .search:
if #available(iOS 16.0, *) {
Expand All @@ -176,6 +177,13 @@ struct ContentView: View {
case .home:
PostingTimelineView(damus_state: damus_state!, home: home, homeEvents: home.events, isSideBarOpened: $isSideBarOpened, active_sheet: $active_sheet, headerOffset: $headerOffset)

case .vines:
if damus_state.settings.vines_feature_enabled {
VineTimelineView(damus_state: damus_state!)
} else {
PostingTimelineView(damus_state: damus_state!, home: home, homeEvents: home.events, isSideBarOpened: $isSideBarOpened, active_sheet: $active_sheet, headerOffset: $headerOffset)
}

case .notifications:
NotificationsView(state: damus, notifications: home.notifications, subtitle: $menu_subtitle)

Expand All @@ -184,9 +192,9 @@ struct ContentView: View {
}
}
.background(DamusColors.adaptableWhite)
.edgesIgnoringSafeArea(selected_timeline != .home ? [] : [.top, .bottom])
.edgesIgnoringSafeArea(immersiveTimeline ? [.top, .bottom] : [])
.navigationBarTitle(timeline_name(selected_timeline), displayMode: .inline)
.toolbar(selected_timeline != .home ? .visible : .hidden)
.toolbar(immersiveTimeline ? .hidden : .visible)
.toolbar {
ToolbarItem(placement: .principal) {
VStack {
Expand All @@ -199,6 +207,18 @@ struct ContentView: View {
.onAppear {
notify(.display_tabbar(true))
}
.onChange(of: damus.settings.vines_feature_enabled) { enabled in
// Fall back to home timeline if vines are disabled while viewing vines
if !enabled && selected_timeline == .vines {
selected_timeline = .home
}
}
.onChange(of: selected_timeline) { timeline in
// Fall back to home timeline if switching to vines when feature is disabled
if timeline == .vines && !damus.settings.vines_feature_enabled {
selected_timeline = .home
}
}
}

func MaybeReportView(target: ReportTarget) -> some View {
Expand Down Expand Up @@ -233,7 +253,9 @@ struct ContentView: View {
}

var body: some View {
VStack(alignment: .leading, spacing: 0) {
let immersiveTimeline = selected_timeline == .home || selected_timeline == .vines

return VStack(alignment: .leading, spacing: 0) {
if let damus = self.damus_state {
NavigationStack(path: $navigationCoordinator.path) {
TabView { // Prevents navbar appearance change on scroll
Expand Down Expand Up @@ -262,7 +284,7 @@ struct ContentView: View {
}
}
.background(DamusColors.adaptableWhite)
.edgesIgnoringSafeArea(selected_timeline != .home ? [] : [.top, .bottom])
.edgesIgnoringSafeArea(immersiveTimeline ? [.top, .bottom] : [])
.tabViewStyle(.page(indexDisplayMode: .never))
.overlay(
SideMenuView(damus_state: damus_state!, isSidebarVisible: $isSideBarOpened.animation(), selected: $selected_timeline)
Expand All @@ -283,7 +305,7 @@ struct ContentView: View {
if !isSideBarOpened {
TabBar(nstatus: home.notification_status, navIsAtRoot: navIsAtRoot(), selected: $selected_timeline, headerOffset: $headerOffset, settings: damus.settings, action: switch_timeline)
.padding([.bottom], 8)
.background(selected_timeline != .home || (selected_timeline == .home && !self.navIsAtRoot()) ? DamusColors.adaptableWhite : DamusColors.adaptableWhite.opacity(abs(1.25 - (abs(headerOffset/100.0)))))
.background(!immersiveTimeline || !self.navIsAtRoot() ? DamusColors.adaptableWhite : DamusColors.adaptableWhite.opacity(abs(1.25 - (abs(headerOffset/100.0)))))
.anchorPreference(key: HeaderBoundsKey.self, value: .bounds){$0}
.overlayPreferenceValue(HeaderBoundsKey.self) { value in
GeometryReader{ proxy in
Expand Down Expand Up @@ -1005,6 +1027,8 @@ func timeline_name(_ timeline: Timeline?) -> String {
switch timeline {
case .home:
return NSLocalizedString("Home", comment: "Navigation bar title for Home view where notes and replies appear from those who the user is following.")
case .vines:
return NSLocalizedString("Vines", comment: "Navigation bar title for Vine video feed.")
case .notifications:
return NSLocalizedString("Notifications", comment: "Toolbar label for Notifications view.")
case .search:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ class NostrNetworkManager {
private var connectionContinuations: [UUID: CheckedContinuation<Void, Never>] = [:]
/// A lock to ensure thread-safe access to the continuations dictionary and connection state
private let continuationsLock = NSLock()

/// Tracks relays that were added by features (not user-configured) so we can safely remove them
/// without deleting user's relay configuration.
/// Isolated to `@MainActor` for thread-safe access without locks.
@MainActor private var featureManagedRelays: Set<RelayURL> = []

init(delegate: Delegate, addNdbToRelayPool: Bool = true) {
self.delegate = delegate
Expand Down Expand Up @@ -264,6 +269,48 @@ class NostrNetworkManager {
.filter { !filters.is_filtered(timeline: .search, relay_id: $0) }
}

/// Ensures the relay pool is connected to a specific relay, adding it if necessary.
///
/// Used by feature-specific code (e.g., Vines) to connect to relays that are
/// not part of the user's configured relay list. Tracks the relay so that
/// ``disconnectRelay(_:)`` can safely remove it later.
func ensureRelayConnected(_ relayURL: RelayURL) async {
if await pool.get_relay(relayURL) != nil {
await MainActor.run { featureManagedRelays.insert(relayURL) }
return
}

let descriptor = RelayPool.RelayDescriptor(url: relayURL, info: .readWrite)
do {
try await pool.add_relay(descriptor)
} catch {
// If the relay already exists we can still connect to it;
// for any other error, log and bail out.
let isAlreadyAdded = await pool.get_relay(relayURL) != nil
if !isAlreadyAdded {
Log.debug("Failed to add relay %s: %s", for: .networking, relayURL.id as CVarArg, error.localizedDescription)
return
}
}
await MainActor.run { featureManagedRelays.insert(relayURL) }
Comment on lines +283 to +295

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Silently swallowing add_relay errors may leave inconsistent state.

try? await pool.add_relay(descriptor) discards the error. If a TOCTOU race causes RelayAlreadyExists, the method still proceeds to connect and marks the relay as feature-managed without confirming it was actually added. Consider handling the error or at least guarding the subsequent lines:

Proposed fix
     let descriptor = RelayPool.RelayDescriptor(url: relayURL, info: .readWrite)
-    try? await pool.add_relay(descriptor)
-    await pool.connect(to: [relayURL])
-    await MainActor.run { featureManagedRelays.insert(relayURL) }
+    do {
+        try await pool.add_relay(descriptor)
+        await pool.connect(to: [relayURL])
+        await MainActor.run { featureManagedRelays.insert(relayURL) }
+    } catch {
+        Log.warning("Failed to add feature relay %s: %s", for: .networking, relayURL.absoluteString, error.localizedDescription)
+    }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let descriptor = RelayPool.RelayDescriptor(url: relayURL, info: .readWrite)
try? await pool.add_relay(descriptor)
await pool.connect(to: [relayURL])
await MainActor.run { featureManagedRelays.insert(relayURL) }
let descriptor = RelayPool.RelayDescriptor(url: relayURL, info: .readWrite)
do {
try await pool.add_relay(descriptor)
await pool.connect(to: [relayURL])
await MainActor.run { featureManagedRelays.insert(relayURL) }
} catch {
Log.warning("Failed to add feature relay %s: %s", for: .networking, relayURL.absoluteString, error.localizedDescription)
}
🤖 Prompt for AI Agents
In `@damus/Core/Networking/NostrNetworkManager/NostrNetworkManager.swift` around
lines 283 - 286, The code silently swallows errors from pool.add_relay which can
leave state inconsistent; change the try? await pool.add_relay(descriptor) to
explicit error handling: call await pool.add_relay(descriptor) inside do/catch,
handle the specific RelayAlreadyExists error by treating it as success (so you
can still proceed to connect), rethrow or log and abort for other errors, and
only insert relayURL into featureManagedRelays after add_relay either succeeded
or you confirmed the relay already exists; keep pool.connect(to:) execution
gated on successful add/confirmed-existence. Use the symbols
RelayPool.RelayDescriptor, pool.add_relay(...), pool.connect(to:), and
featureManagedRelays.insert(relayURL) to locate and update the logic.

await pool.connect(to: [relayURL])
}
Comment on lines +272 to +297

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

ensureRelayConnected marks pre-existing (user-configured) relays as feature-managed, allowing disconnectRelay to remove them.

When the relay already exists in the pool (Lines 278-280), it's unconditionally added to featureManagedRelays. If the user independently configured wss://relay.divine.video, toggling the Vine feature off would call disconnectRelay and remove their relay.

Only insert into featureManagedRelays when you actually added the relay:

Proposed fix
 func ensureRelayConnected(_ relayURL: RelayURL) async {
     if await pool.get_relay(relayURL) != nil {
-        await MainActor.run { featureManagedRelays.insert(relayURL) }
+        // Relay already exists — don't claim ownership
         return
     }
 
     let descriptor = RelayPool.RelayDescriptor(url: relayURL, info: .readWrite)
     try? await pool.add_relay(descriptor)
     await pool.connect(to: [relayURL])
     await MainActor.run { featureManagedRelays.insert(relayURL) }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// Ensures the relay pool is connected to a specific relay, adding it if necessary.
///
/// Used by feature-specific code (e.g., Vines) to connect to relays that are
/// not part of the user's configured relay list. Tracks the relay so that
/// ``disconnectRelay(_:)`` can safely remove it later.
func ensureRelayConnected(_ relayURL: RelayURL) async {
if await pool.get_relay(relayURL) != nil {
await MainActor.run { featureManagedRelays.insert(relayURL) }
return
}
let descriptor = RelayPool.RelayDescriptor(url: relayURL, info: .readWrite)
try? await pool.add_relay(descriptor)
await pool.connect(to: [relayURL])
await MainActor.run { featureManagedRelays.insert(relayURL) }
}
/// Ensures the relay pool is connected to a specific relay, adding it if necessary.
///
/// Used by feature-specific code (e.g., Vines) to connect to relays that are
/// not part of the user's configured relay list. Tracks the relay so that
/// ``disconnectRelay(_:)`` can safely remove it later.
func ensureRelayConnected(_ relayURL: RelayURL) async {
if await pool.get_relay(relayURL) != nil {
// Relay already exists — don't claim ownership
return
}
let descriptor = RelayPool.RelayDescriptor(url: relayURL, info: .readWrite)
try? await pool.add_relay(descriptor)
await pool.connect(to: [relayURL])
await MainActor.run { featureManagedRelays.insert(relayURL) }
}
🤖 Prompt for AI Agents
In `@damus/Core/Networking/NostrNetworkManager/NostrNetworkManager.swift` around
lines 272 - 287, ensureRelayConnected currently marks relays that already exist
in the pool as feature-managed (via featureManagedRelays.insert(relayURL)) and
so user-configured relays can be later removed by disconnectRelay; change the
logic in ensureRelayConnected(_:): if await pool.get_relay(relayURL) != nil just
return without inserting into featureManagedRelays, and only insert into
featureManagedRelays after you actually add the relay (i.e. after a successful
try await pool.add_relay(descriptor) or by verifying the relay was newly present
post-add/connect); reference ensureRelayConnected, pool.get_relay(_:),
pool.add_relay(_:), pool.connect(to:), featureManagedRelays, and
disconnectRelay(_:) when making this change.


/// Disconnects and removes a relay that was previously added via ``ensureRelayConnected(_:)``.
///
/// Only removes relays tracked as feature-managed — user-configured relays are left untouched.
func disconnectRelay(_ relayURL: RelayURL) async {
let isFeatureManaged = await MainActor.run { featureManagedRelays.contains(relayURL) }
guard isFeatureManaged else {
Log.debug("Skipping removal of relay %s - not feature-managed", for: .networking, relayURL.id as CVarArg)
return
}

guard await pool.get_relay(relayURL) != nil else { return }
await pool.remove_relay(relayURL)
await MainActor.run { featureManagedRelays.remove(relayURL) }
}

// MARK: NWC
// TODO: Move this to NWCManager

Expand Down
1 change: 1 addition & 0 deletions damus/Core/Nostr/NostrKind.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ enum NostrKind: UInt32, Codable {
case list_deprecated = 30000
case draft = 31234
case longform = 30023
case vine_short = 34236
case zap = 9735
case zap_request = 9734
case highlight = 9802
Expand Down
5 changes: 5 additions & 0 deletions damus/Core/Nostr/RelayURL.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ public struct RelayURL: Hashable, Equatable, Codable, CodingKeyRepresentable, Id

}

extension RelayURL {
/// Shared relay used by the Vine short-video feed.
static let vineRelay = RelayURL("wss://relay.divine.video")!
}

private struct StringKey: CodingKey {
var stringValue: String
init(stringValue: String) {
Expand Down
2 changes: 1 addition & 1 deletion damus/Features/Events/Models/LoadableNostrEventView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ class LoadableNostrEventViewModel: ObservableObject {
case .zap, .zap_request:
guard let zap = await get_zap(from: ev, state: damus_state) else { return .not_found }
return .loaded(route: Route.Zaps(target: zap.target))
case .contacts, .metadata, .delete, .boost, .chat, .mute_list, .list_deprecated, .draft, .nwc_request, .nwc_response, .http_auth, .status, .relay_list, .follow_list, .interest_list, .contact_card, .live, .live_chat:
case .contacts, .metadata, .delete, .boost, .chat, .mute_list, .list_deprecated, .draft, .nwc_request, .nwc_response, .http_auth, .status, .relay_list, .follow_list, .interest_list, .contact_card, .live, .live_chat, .vine_short:
return .unknown_or_unsupported_kind
}
case .naddr(let naddr):
Expand Down
8 changes: 4 additions & 4 deletions damus/Features/Follows/Models/Contacts.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ class Contacts {
func remove_friend(_ pubkey: Pubkey) {
friends.remove(pubkey)

pubkey_to_our_friends.forEach {
pubkey_to_our_friends[$0.key]?.remove(pubkey)
for key in pubkey_to_our_friends.keys {
pubkey_to_our_friends[key]?.remove(pubkey)
}
}

Expand All @@ -56,7 +56,7 @@ class Contacts {
func add_friend_pubkey(_ pubkey: Pubkey) {
friends.insert(pubkey)
}

func add_friend_contact(_ contact: NostrEvent) {
friends.insert(contact.pubkey)
for pk in contact.referenced_pubkeys {
Expand All @@ -76,7 +76,7 @@ class Contacts {
func is_friend_of_friend(_ pubkey: Pubkey) -> Bool {
return friend_of_friends.contains(pubkey)
}

func is_in_friendosphere(_ pubkey: Pubkey) -> Bool {
return friends.contains(pubkey) || friend_of_friends.contains(pubkey)
}
Expand Down
20 changes: 20 additions & 0 deletions damus/Features/Labs/Views/DamusLabsExperiments.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,13 @@ struct DamusLabsExperiments: View {
@ObservedObject var settings: UserSettingsStore
@State var show_live_explainer: Bool = false
@State var show_favorites_explainer: Bool = false
@State private var show_vines_explainer: Bool = false
@State private var show_vine_prefetch_explainer: Bool = false

let live_label = NSLocalizedString("Live", comment: "Label for a toggle that enables an experimental feature")
let favorites_label = NSLocalizedString("Favorites", comment: "Label for a toggle that enables an experimental feature")
let vines_label = NSLocalizedString("Vines", comment: "Label for a toggle that enables an experimental feature")
let vines_prefetch_label = NSLocalizedString("Prefetch vines on cellular", comment: "Label for a toggle that allows vine prefetching on cellular.")

var body: some View {
ScrollView {
Expand Down Expand Up @@ -44,6 +48,10 @@ struct DamusLabsExperiments: View {

LabsToggleView(toggleName: live_label, systemImage: "record.circle", isOn: $settings.live, showInfo: $show_live_explainer)
LabsToggleView(toggleName: favorites_label, systemImage: "heart.fill", isOn: $settings.enable_favourites_feature, showInfo: $show_favorites_explainer)
LabsToggleView(toggleName: vines_label, systemImage: "video", isOn: $settings.enable_vine_feature, showInfo: $show_vines_explainer)
if settings.enable_vine_feature {
LabsToggleView(toggleName: vines_prefetch_label, systemImage: "antenna.radiowaves.left.and.right", isOn: $settings.prefetch_vines_on_cellular, showInfo: $show_vine_prefetch_explainer)
}

}
.padding([.trailing, .leading], 20)
Expand All @@ -67,6 +75,18 @@ struct DamusLabsExperiments: View {
systemImage: "heart.fill",
labDescription: NSLocalizedString("This will allow you to pick users to be part of your favorites list. You can also switch your profile timeline to only see posts from your favorite contacts.", comment: "Damus Labs feature explanation"))
}
.sheet(isPresented: $show_vines_explainer) {
LabsExplainerView(
labName: vines_label,
systemImage: "video",
labDescription: NSLocalizedString("Enables the Vines tab so you can browse short Divine videos inside Damus. This is still experimental and requires the Divine relay.", comment: "Damus Labs feature explanation"))
}
.sheet(isPresented: $show_vine_prefetch_explainer) {
LabsExplainerView(
labName: vines_prefetch_label,
systemImage: "antenna.radiowaves.left.and.right",
labDescription: NSLocalizedString("Prefetches upcoming Vines even on cellular connections. This may use additional mobile data.", comment: "Explainer for Vine cellular prefetch toggle."))
}
}
}

Expand Down
11 changes: 6 additions & 5 deletions damus/Features/Posting/Views/PostView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -604,7 +604,7 @@ struct PostView: View {
// initiate asynchronous uploading Task for multiple-images
let task = Task {
for media in preUploadedMedia {
if let mediaToUpload = generateMediaUpload(media) {
if let mediaToUpload = await generateMediaUpload(media) {
await self.handle_upload(media: mediaToUpload)
}
}
Expand All @@ -626,10 +626,11 @@ struct PostView: View {
// This alert seeks confirmation about Image-upload when user taps Paste option
.alert(NSLocalizedString("Are you sure you want to upload this media?", comment: "Alert message asking if the user wants to upload media."), isPresented: $imageUploadConfirmPasteboard) {
Button(NSLocalizedString("Upload", comment: "Button to proceed with uploading."), role: .none) {
if let image = imagePastedFromPasteboard,
let mediaToUpload = generateMediaUpload(image) {
if let image = imagePastedFromPasteboard {
let task = Task {
_ = await self.handle_upload(media: mediaToUpload)
if let mediaToUpload = await generateMediaUpload(image) {
_ = await self.handle_upload(media: mediaToUpload)
}
}
uploadTasks.append(task)
}
Expand All @@ -641,7 +642,7 @@ struct PostView: View {
Button(NSLocalizedString("Upload", comment: "Button to proceed with uploading."), role: .none) {
let task = Task {
for media in preUploadedMedia {
if let mediaToUpload = generateMediaUpload(media) {
if let mediaToUpload = await generateMediaUpload(media) {
await self.handle_upload(media: mediaToUpload)
}
}
Expand Down
Loading