Skip to content

Commit

Permalink
Merge branch 'feature/edit-lists-ui'
Browse files Browse the repository at this point in the history
  • Loading branch information
mplorentz committed Jan 23, 2025
2 parents a374614 + 99f4b48 commit f8311df
Show file tree
Hide file tree
Showing 33 changed files with 1,241 additions and 184 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Release Notes
- Fixed: adding/removing relays not reflected on feed filter. [#119](https://github.com/verse-pbc/issues/issues/119)
- Added Lists view and two ways to navigate to it. [#133](https://github.com/verse-pbc/issues/issues/133)
- Added view for editing a list's title and description. [#134](https://github.com/verse-pbc/issues/issues/134)
- Added List detail view. [#155](https://github.com/verse-pbc/issues/issues/155)
- Added view for managing users in a list. [#135](https://github.com/verse-pbc/issues/issues/135)
- Added ability to delete lists. [#136](https://github.com/verse-pbc/issues/issues/136)
- Added analytics for feed source selection and lists. [#129](https://github.com/verse-pbc/issues/issues/129)

### Internal Changes
- Added function for creating a new list and a test verifying list editing. [#112](https://github.com/verse-pbc/issues/issues/112)
- Localized strings on the feed filter drop-down view.
- Disabled automatic tracking in Sentry. [#126](https://github.com/verse-pbc/issues/issues/126)
- Improved naming of a couple list-related classes.
- Track TestFlight vs AppStore installations in Posthog. [#130](https://github.com/verse-pbc/issues/issues/130)
- Track breadcrumbs in Sentry for all analytics events. [#125](https://github.com/verse-pbc/issues/issues/125)

Expand Down
57 changes: 45 additions & 12 deletions Nos.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

144 changes: 144 additions & 0 deletions Nos/Assets/Localization/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -4766,6 +4766,17 @@
}
}
},
"deleteList" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Delete List"
}
}
}
},
"deleteMyAccount" : {
"extractionState" : "manual",
"localizations" : {
Expand Down Expand Up @@ -5833,6 +5844,17 @@
}
}
},
"editListInfo" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Edit List Info"
}
}
}
},
"editProfile" : {
"extractionState" : "manual",
"localizations" : {
Expand Down Expand Up @@ -10434,6 +10456,17 @@
}
}
},
"listName" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "List Name"
}
}
}
},
"lists" : {
"extractionState" : "manual",
"localizations" : {
Expand All @@ -10445,6 +10478,39 @@
}
}
},
"listsDescription" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Add your favorite users to public lists and pin them to your home feed"
}
}
}
},
"listStep1" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Step 1: list info"
}
}
}
},
"listStep2" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Step 2: add users"
}
}
}
},
"loading" : {
"extractionState" : "manual",
"localizations" : {
Expand Down Expand Up @@ -10920,6 +10986,28 @@
}
}
},
"manageUsers" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Manage Users"
}
}
}
},
"manageYourLists" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Manage Your Lists"
}
}
}
},
"mention" : {
"extractionState" : "manual",
"localizations" : {
Expand Down Expand Up @@ -11554,6 +11642,17 @@
}
}
},
"newList" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "New List"
}
}
}
},
"newNote" : {
"extractionState" : "manual",
"localizations" : {
Expand Down Expand Up @@ -12270,6 +12369,17 @@
}
}
},
"noDescription" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "No Description"
}
}
}
},
"noEvents" : {
"extractionState" : "manual",
"localizations" : {
Expand Down Expand Up @@ -20223,6 +20333,29 @@
}
}
},
"xUsers" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"variations" : {
"plural" : {
"one" : {
"stringUnit" : {
"state" : "translated",
"value" : "%1$lld user"
}
},
"other" : {
"stringUnit" : {
"state" : "translated",
"value" : "%1$lld users"
}
}
}
}
}
}
},
"yes" : {
"extractionState" : "manual",
"localizations" : {
Expand Down Expand Up @@ -20401,6 +20534,17 @@
}
}
},
"yourLists" : {
"extractionState" : "manual",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Your Lists"
}
}
}
},
"yourProfile" : {
"extractionState" : "manual",
"localizations" : {
Expand Down
18 changes: 7 additions & 11 deletions Nos/Controller/FeedController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import SwiftUI
private(set) var listRowItems: [FeedToggleRow.Item] = []
private(set) var relayRowItems: [FeedToggleRow.Item] = []

private var lists: [AuthorList] = [] {
private(set) var lists: [AuthorList] = [] {
didSet {
updateEnabledSources()
}
Expand All @@ -38,16 +38,8 @@ import SwiftUI
}

@ObservationIgnored private lazy var listsPublisher = {
let request = NSFetchRequest<AuthorList>(entityName: "AuthorList")
request.sortDescriptors = [NSSortDescriptor(keyPath: \Event.createdAt, ascending: false)]
request.predicate = NSPredicate(
format: "kind = %i AND author = %@ AND title != nil",
EventKind.followSet.rawValue,
author
)

let listWatcher = NSFetchedResultsController(
fetchRequest: request,
fetchRequest: AuthorList.authorLists(ownedBy: author),
managedObjectContext: persistenceController.viewContext,
sectionNameKeyPath: nil,
cacheName: "FeedController.listWatcher"
Expand Down Expand Up @@ -90,7 +82,11 @@ import SwiftUI
.publisher
.receive(on: DispatchQueue.main)
.sink(receiveValue: { [weak self] lists in
self?.lists = lists
// ensure that we only publish the most recent list for each replaceable identifier
let grouped = Dictionary(grouping: lists, by: { $0.replaceableIdentifier ?? "" })
self?.lists = grouped.compactMap { _, events in
events.max(by: { $0.createdAt ?? Date.distantPast < $1.createdAt ?? Date.distantPast })
}
})
.store(in: &cancellables)
}
Expand Down
7 changes: 6 additions & 1 deletion Nos/Controller/SearchController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,11 @@ enum SearchState {
enum SearchOrigin {
/// Search initiated from the Discover tab
case discover

/// Search initiated from ``AuthorListManageUsersView``
case lists

/// Search initiated from the mentions `AuthorListView`
/// Search initiated from the mentions `AuthorSearchView`
case mentions
}

Expand Down Expand Up @@ -91,6 +94,8 @@ enum SearchOrigin {
switch searchOrigin {
case .discover:
analytics.searchedDiscover()
case .lists:
break // TODO: Analytics
case .mentions:
analytics.mentionsAutocompleteCharactersEntered()
}
Expand Down
13 changes: 12 additions & 1 deletion Nos/Models/CoreData/AuthorList+CoreDataClass.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,23 @@ public class AuthorList: Event {
owner,
kind
)
fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \AuthorList.identifier, ascending: true)]
fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \Event.createdAt, ascending: false)]
fetchRequest.fetchLimit = 1
return fetchRequest
}

var allAuthors: Set<Author> {
authors.union(privateAuthors)
}

static func authorLists(ownedBy owner: Author) -> NSFetchRequest<AuthorList> {
let request = NSFetchRequest<AuthorList>(entityName: "AuthorList")
request.sortDescriptors = [NSSortDescriptor(keyPath: \Event.createdAt, ascending: false)]
request.predicate = NSPredicate(
format: "kind = %i AND author = %@ AND title != nil AND title != '' AND deletedOn.@count = 0",
EventKind.followSet.rawValue,
owner
)
return request
}
}
29 changes: 29 additions & 0 deletions Nos/Models/CoreData/Event+CoreDataClass.swift
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,35 @@ public class Event: NosManagedObject, VerifiableEvent {
deletedEvent.deletedOn.insert(relay)
}
}

// track deleted replaceable events
// ["a", "<kind>:<pubkey>:<d-identifier>"]
if let author, let createdAt {
let aTags = tags.filter { $0.first == "a" && $0.count >= 2 }

for aTag in aTags.map({ $0[1] }) {
let components = aTag.split(separator: ":").map { String($0) }
let pubkey = components[1]

guard pubkey == author.hexadecimalPublicKey else {
// ensure that this delete event only affects events with the author's pubkey
continue
}

guard let kind = Int64(components[0]) else {
continue
}

let replaceableID = components[2]
let request = Event.event(by: replaceableID, author: author, kind: kind, before: createdAt)

let results = try context.fetch(request)
for event in results {
print("\(event.identifier ?? "n/a") was deleted on \(relay.address ?? "unknown")")
event.deletedOn.insert(relay)
}
}
}
}

class func requestAuthorsMetadataIfNeeded(
Expand Down
27 changes: 20 additions & 7 deletions Nos/Models/CoreData/Event+Fetching.swift
Original file line number Diff line number Diff line change
Expand Up @@ -271,16 +271,29 @@ extension Event {
@nonobjc public class func event(
by replaceableID: RawReplaceableID,
author: Author,
kind: Int64
kind: Int64,
before: Date? = nil
) -> NSFetchRequest<Event> {
let fetchRequest = NSFetchRequest<Event>(entityName: "Event")
fetchRequest.predicate = NSPredicate(
format: "replaceableIdentifier = %@ AND author = %@ AND kind = %i",
replaceableID,
author,
kind
var andPredicates = [
NSPredicate(
format: "replaceableIdentifier = %@ AND author = %@ AND kind = %i",
replaceableID,
author,
kind
)
]

if let before {
andPredicates.append(NSPredicate(format: "createdAt <= %@", before as CVarArg))
}

fetchRequest.predicate = NSCompoundPredicate(
type: .and,
subpredicates: andPredicates
)
fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \Event.replaceableIdentifier, ascending: true)]

fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \Event.createdAt, ascending: false)]
fetchRequest.fetchLimit = 1
return fetchRequest
}
Expand Down
Loading

0 comments on commit f8311df

Please sign in to comment.