diff --git a/Flipcash/Core/Screens/Conversation/ChatScreenRepresentable.swift b/Flipcash/Core/Screens/Conversation/ChatScreenRepresentable.swift index 31e0e2606..9b071d1f8 100644 --- a/Flipcash/Core/Screens/Conversation/ChatScreenRepresentable.swift +++ b/Flipcash/Core/Screens/Conversation/ChatScreenRepresentable.swift @@ -32,8 +32,8 @@ struct ChatScreenRepresentable: UIViewControllerRepresentable { /// Fired when the user taps an `@handle` in a message. The owner looks the handle up and opens /// that person's profile. let onMentionTap: (Username) -> Void - /// Fired when the Share button on a shared-profile widget is tapped. - let onShareProfile: (LinkCard.User) -> Void + /// Fired when the Share button on a shared-profile widget is tapped, with the name it shows once resolved. + let onShareProfile: (LinkCard.User, String?) -> Void let ownProfile: OwnProfileCard? /// Fired when the user taps the card drawn in place of a link, with the whole card and the /// stable id of the message it came on. The owner decides where it lands, which differs by kind. diff --git a/Flipcash/Core/Screens/Conversation/ConversationScreen.swift b/Flipcash/Core/Screens/Conversation/ConversationScreen.swift index 566c4bd12..8e87d339c 100644 --- a/Flipcash/Core/Screens/Conversation/ConversationScreen.swift +++ b/Flipcash/Core/Screens/Conversation/ConversationScreen.swift @@ -1285,8 +1285,8 @@ struct ConversationScreen: View { } /// Opens the share sheet on the widget's person, with the public link their own You tab shares. - private func shareProfile(_ card: LinkCard.User) { - let item = TipCodeShareItem(url: card.url, title: card.linkedHandle ?? card.url.absoluteString, preview: nil) + private func shareProfile(_ card: LinkCard.User, displayName: String?) { + let item = TipCodeShareItem.profile(url: card.url, displayName: displayName) ShareSheet.present(activityItem: item) { _ in } } diff --git a/Flipcash/Core/Screens/Main/Tips/TipcardScreen.swift b/Flipcash/Core/Screens/Main/Tips/TipcardScreen.swift index bb139edc8..86583f51c 100644 --- a/Flipcash/Core/Screens/Main/Tips/TipcardScreen.swift +++ b/Flipcash/Core/Screens/Main/Tips/TipcardScreen.swift @@ -127,19 +127,10 @@ struct TipcardScreen: View { // MARK: - Sharing - - /// The label iOS shows beside the preview; the sharer's name, not the URL. - private var shareTitle: String { - if let name = profile?.displayName, !name.isEmpty { - "Tip \(name)" - } else { - "My Tip Card" - } - } - private func shareTipCard() { - let item = TipCodeShareItem( + let item = TipCodeShareItem.profile( url: url, - title: shareTitle, + displayName: profile?.displayName, preview: previewCache.preview(for: sessionContainer.session.userID) ) diff --git a/Flipcash/Core/Screens/Main/You/YouScreen.swift b/Flipcash/Core/Screens/Main/You/YouScreen.swift index 9afc51100..71a5f8908 100644 --- a/Flipcash/Core/Screens/Main/You/YouScreen.swift +++ b/Flipcash/Core/Screens/Main/You/YouScreen.swift @@ -636,14 +636,10 @@ struct YouScreen: View { } } - private var shareTitle: String { - if let displayName { "Tip \(displayName)" } else { "My Tip Card" } - } - private func shareTipCard() { - let item = TipCodeShareItem( + let item = TipCodeShareItem.profile( url: url, - title: shareTitle, + displayName: displayName, preview: previewCache.preview(for: sessionContainer.session.userID) ) ShareSheet.present(activityItem: item) { _ in } diff --git a/Flipcash/Core/Screens/Profile/UserProfileScreen.swift b/Flipcash/Core/Screens/Profile/UserProfileScreen.swift index 3c6d8a0a3..907dbd597 100644 --- a/Flipcash/Core/Screens/Profile/UserProfileScreen.swift +++ b/Flipcash/Core/Screens/Profile/UserProfileScreen.swift @@ -349,11 +349,7 @@ final class UserProfileViewModel { /// Opens the share sheet on this person's public link, the one their own You tab shares. func share() { - let item = TipCodeShareItem( - url: .tipcard(for: userID, username: username), - title: displayName, - preview: nil - ) + let item = TipCodeShareItem.profile(url: .tipcard(for: userID, username: username), displayName: name) ShareSheet.present(activityItem: item) { _ in } } diff --git a/Flipcash/Share/TipCodeShareItem.swift b/Flipcash/Share/TipCodeShareItem.swift index f3cdca44d..0b0478ddc 100644 --- a/Flipcash/Share/TipCodeShareItem.swift +++ b/Flipcash/Share/TipCodeShareItem.swift @@ -14,16 +14,27 @@ import LinkPresentation final class TipCodeShareItem: NSObject, UIActivityItemSource { private let url: URL - private let title: String + private let title: String? private let preview: TipCodePreview? - init(url: URL, title: String, preview: TipCodePreview?) { + init(url: URL, title: String?, preview: TipCodePreview?) { self.url = url self.title = title self.preview = preview super.init() } + /// Shares a person's profile link, titled for them — the one way every profile share is built. + static func profile(url: URL, displayName: String?, preview: TipCodePreview? = nil) -> TipCodeShareItem { + TipCodeShareItem(url: url, title: profileTitle(for: displayName), preview: preview) + } + + /// "Chat with on Flipcash", or nil when there is no name, so the link goes untitled. + static func profileTitle(for displayName: String?) -> String? { + guard let displayName, !displayName.isEmpty else { return nil } + return String(localized: "Chat with \(displayName) on Flipcash", comment: "Share sheet title for a person's profile link") + } + // MARK: - UIActivityItemSource - func activityViewControllerPlaceholderItem(_ activityViewController: UIActivityViewController) -> Any { @@ -35,7 +46,7 @@ final class TipCodeShareItem: NSObject, UIActivityItemSource { } func activityViewController(_ activityViewController: UIActivityViewController, subjectForActivityType activityType: UIActivity.ActivityType?) -> String { - title + title ?? "" } func activityViewControllerLinkMetadata(_ activityViewController: UIActivityViewController) -> LPLinkMetadata? { diff --git a/FlipcashTests/ProfileShareTitleTests.swift b/FlipcashTests/ProfileShareTitleTests.swift new file mode 100644 index 000000000..f4fa81c66 --- /dev/null +++ b/FlipcashTests/ProfileShareTitleTests.swift @@ -0,0 +1,23 @@ +// +// ProfileShareTitleTests.swift +// FlipcashTests +// + +import Foundation +import Testing +@testable import Flipcash + +@MainActor +@Suite("Profile share title") +struct ProfileShareTitleTests { + + @Test("A named profile is titled as an invitation to chat") + func named() { + #expect(TipCodeShareItem.profileTitle(for: "Ada") == "Chat with Ada on Flipcash") + } + + @Test("A profile without a name shares untitled", arguments: [nil, ""] as [String?]) + func unnamed(displayName: String?) { + #expect(TipCodeShareItem.profileTitle(for: displayName) == nil) + } +} diff --git a/FlipcashUI/Sources/FlipcashUI/Chat/ChatScreenViewController.swift b/FlipcashUI/Sources/FlipcashUI/Chat/ChatScreenViewController.swift index 6be732a6b..a409e173f 100644 --- a/FlipcashUI/Sources/FlipcashUI/Chat/ChatScreenViewController.swift +++ b/FlipcashUI/Sources/FlipcashUI/Chat/ChatScreenViewController.swift @@ -167,7 +167,7 @@ public final class ChatScreenViewController: UIViewController { set { transcript.ownProfile = newValue } } - public var onShareProfile: ((LinkCard.User) -> Void)? { + public var onShareProfile: ((LinkCard.User, String?) -> Void)? { get { transcript.onShareProfile } set { transcript.onShareProfile = newValue } } diff --git a/FlipcashUI/Sources/FlipcashUI/Chat/ChatShareProfileCell.swift b/FlipcashUI/Sources/FlipcashUI/Chat/ChatShareProfileCell.swift index c0aa41db5..7fd3f2dfb 100644 --- a/FlipcashUI/Sources/FlipcashUI/Chat/ChatShareProfileCell.swift +++ b/FlipcashUI/Sources/FlipcashUI/Chat/ChatShareProfileCell.swift @@ -39,8 +39,9 @@ public final class ChatShareProfileCell: ChatColumnCell { /// which is where the card subscribes. weak var linkCardSource: (any LinkCardSource)? - /// Called when the Share button is tapped, with the card whose handle is being shared. - var onShare: ((LinkCard.User) -> Void)? + /// Called when the Share button is tapped, with the card being shared and the person's display + /// name, nil until the lookup resolves one. + var onShare: ((LinkCard.User, String?) -> Void)? private let card = BubbleBackgroundView() private let reactionRow = ReactionPillRowView() @@ -167,10 +168,17 @@ public final class ChatShareProfileCell: ChatColumnCell { } } + private static func displayName(_ state: LinkCard.User.State?) -> String? { + switch state { + case .resolved(let resolved): resolved.displayName.isEmpty ? nil : resolved.displayName + case .notFound, nil: nil + } + } + private func draw(_ profile: LinkCard.User, state: LinkCard.User.State?) { content.configuration = UIHostingConfiguration { ShareProfileWidgetView(username: profile.linkedHandle, state: state) { [weak self] in - self?.onShare?(profile) + self?.onShare?(profile, Self.displayName(state)) } } .margins(.all, 0) diff --git a/FlipcashUI/Sources/FlipcashUI/Chat/ChatViewController.swift b/FlipcashUI/Sources/FlipcashUI/Chat/ChatViewController.swift index 8ddd794bb..7cfe38c5c 100644 --- a/FlipcashUI/Sources/FlipcashUI/Chat/ChatViewController.swift +++ b/FlipcashUI/Sources/FlipcashUI/Chat/ChatViewController.swift @@ -51,8 +51,9 @@ public final class ChatViewController: UICollectionViewController { /// Called when the user taps a URL in a text bubble; the owner opens it. public var onOpenURL: ((URL) -> Void)? - /// Fired when a shared-profile widget's Share button is tapped. - public var onShareProfile: ((LinkCard.User) -> Void)? + /// Fired when a shared-profile widget's Share button is tapped, with the person's display name + /// once the lookup has resolved it. + public var onShareProfile: ((LinkCard.User, String?) -> Void)? /// The viewer's own profile, which a shared-profile widget naming them draws from directly. Rows /// on screen pick a change up without a diff. @@ -666,7 +667,7 @@ public final class ChatViewController: UICollectionViewController { case let cell as ChatShareProfileCell: cell.linkCardSource = linkCardSource cell.ownProfile = ownProfile - cell.onShare = { [weak self] card in self?.onShareProfile?(card) } + cell.onShare = { [weak self] card, name in self?.onShareProfile?(card, name) } let cardWidth = available - ChatColumnCell.rowInset * 2 cell.configure(with: message, maxWidth: cardWidth, authorImageData: authorImageData) cell.onReactionTap = { [weak self] emoji in self?.onReactionTap?(message.messageID, emoji) }