crossmate

A collaborative crossword app for iOS
Log | Files | Refs | LICENSE

FriendEntity+DisplayName.swift (3425B)


      1 import CoreData
      2 import Foundation
      3 
      4 extension FriendEntity {
      5     /// The name the invite surfaces should show for this friend.
      6     ///
      7     /// A `nickname` the user assigned via Rename wins outright — it's their
      8     /// private label for the friend and never follows the friend's own
      9     /// renames. Otherwise the friend's own name (`givenDisplayName`), then
     10     /// the "Player" placeholder.
     11     var resolvedDisplayName: String {
     12         if let nickname = nickname?.trimmingCharacters(in: .whitespacesAndNewlines),
     13            !nickname.isEmpty {
     14             return nickname
     15         }
     16         return givenDisplayName ?? "Player"
     17     }
     18 
     19     /// The friend's own, self-chosen name — `resolvedDisplayName` without the
     20     /// local nickname override, and the substring the nickname directory
     21     /// rewrites in sender-composed notification text.
     22     ///
     23     /// `displayName` is fed exclusively by the friend's `name` Decision in the
     24     /// pairwise friend zone (`RecordSerializer.applyDecisionRecord`), so it is
     25     /// the live, rename-following value. Until the first Decision syncs the
     26     /// fallback is the freshest per-game `Player` snapshot the friend wrote at
     27     /// game open; `nil` when neither has arrived.
     28     var givenDisplayName: String? {
     29         if let name = displayName?.trimmingCharacters(in: .whitespacesAndNewlines),
     30            !name.isEmpty {
     31             return name
     32         }
     33         if let authorID, !authorID.isEmpty, let ctx = managedObjectContext {
     34             let req = NSFetchRequest<PlayerEntity>(entityName: "PlayerEntity")
     35             req.predicate = NSPredicate(
     36                 format: "authorID == %@ AND name != nil AND name != %@",
     37                 authorID, ""
     38             )
     39             req.sortDescriptors = [NSSortDescriptor(key: "updatedAt", ascending: false)]
     40             req.fetchLimit = 1
     41             if let name = (try? ctx.fetch(req).first)?.name?
     42                 .trimmingCharacters(in: .whitespacesAndNewlines),
     43                !name.isEmpty {
     44                 return name
     45             }
     46         }
     47         return nil
     48     }
     49 
     50     /// Rewrites the App Group nickname directory from Core Data ground truth:
     51     /// one `authorID → nickname` entry per unblocked friend with a nickname.
     52     /// The Notification Service Extension pairs these with the live
     53     /// `PushPayload.senderName` to swap the friend's own name for the nickname
     54     /// in sender-composed alert text. Called after a local rename, after sync
     55     /// applies a `nickname` Decision, and once at launch as a heal. Must run
     56     /// inside the context's queue (`performAndWait`) when `ctx` is a background
     57     /// context.
     58     static func rebuildNicknameDirectory(in ctx: NSManagedObjectContext) {
     59         let req = NSFetchRequest<FriendEntity>(entityName: "FriendEntity")
     60         req.predicate = NSPredicate(
     61             format: "isBlocked == NO AND nickname != nil AND nickname != %@", ""
     62         )
     63         var directory: [String: NicknameDirectory.Entry] = [:]
     64         for friend in (try? ctx.fetch(req)) ?? [] {
     65             guard let authorID = friend.authorID, !authorID.isEmpty,
     66                   let nickname = friend.nickname?
     67                       .trimmingCharacters(in: .whitespacesAndNewlines),
     68                   !nickname.isEmpty
     69             else { continue }
     70             directory[authorID] = NicknameDirectory.Entry(nickname: nickname)
     71         }
     72         NicknameDirectory.save(directory)
     73     }
     74 }