diff --git a/unstoppable-ios-app/domains-manager-ios-preview/Entities/PreviewNetworkService.swift b/unstoppable-ios-app/domains-manager-ios-preview/Entities/PreviewNetworkService.swift index 0cd8fb565..88a4a5b32 100644 --- a/unstoppable-ios-app/domains-manager-ios-preview/Entities/PreviewNetworkService.swift +++ b/unstoppable-ios-app/domains-manager-ios-preview/Entities/PreviewNetworkService.swift @@ -177,8 +177,7 @@ extension NetworkService { imageType: nil, coverPath: nil, phoneNumber: nil, - domainPurchased: nil, - udBlue: nil), + domainPurchased: nil), socialAccounts: nil, referralCode: nil, social: nil, diff --git a/unstoppable-ios-app/domains-manager-ios.xcodeproj/project.pbxproj b/unstoppable-ios-app/domains-manager-ios.xcodeproj/project.pbxproj index f99677609..368c61dff 100644 --- a/unstoppable-ios-app/domains-manager-ios.xcodeproj/project.pbxproj +++ b/unstoppable-ios-app/domains-manager-ios.xcodeproj/project.pbxproj @@ -11017,7 +11017,7 @@ repositoryURL = "https://github.com/Oleg-Pecheneg/push-swift-sdk"; requirement = { kind = revision; - revision = ff3151a6834db9ca841ee0564979298f4b0df053; + revision = 7aa3a1aeea89acd91dcbefcb0473a2a993a66334; }; }; C66811F02B47B0F500BDABB0 /* XCRemoteSwiftPackageReference "xmtp-ios" */ = { diff --git a/unstoppable-ios-app/domains-manager-ios.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/unstoppable-ios-app/domains-manager-ios.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index db3970c4a..3a8a9428f 100644 --- a/unstoppable-ios-app/domains-manager-ios.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/unstoppable-ios-app/domains-manager-ios.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -131,7 +131,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/Oleg-Pecheneg/push-swift-sdk", "state" : { - "revision" : "ff3151a6834db9ca841ee0564979298f4b0df053" + "revision" : "7aa3a1aeea89acd91dcbefcb0473a2a993a66334" } }, { diff --git a/unstoppable-ios-app/domains-manager-ios/Extensions/Extension-String+Preview.swift b/unstoppable-ios-app/domains-manager-ios/Extensions/Extension-String+Preview.swift index b89f95988..596fa6d60 100644 --- a/unstoppable-ios-app/domains-manager-ios/Extensions/Extension-String+Preview.swift +++ b/unstoppable-ios-app/domains-manager-ios/Extensions/Extension-String+Preview.swift @@ -28,7 +28,6 @@ extension String { case unableToCreateAccountTutorial case referralTutorial case referralLink(code: String) - case udBlue case communitiesInfo case setupApplePayInstruction case unstoppableDomainSearch(searchKey: String) @@ -92,8 +91,6 @@ extension String { return "https://unstoppabledomains.com/refer-a-friend" case .referralLink(let code): return "\(NetworkConfig.migratedBaseUrl)/?ref=\(code)" - case .udBlue: - return "https://unstoppabledomains.com/products/blue" case .communitiesInfo: return "https://support.unstoppabledomains.com/support/solutions/articles/48001215751-badges" case .setupApplePayInstruction: diff --git a/unstoppable-ios-app/domains-manager-ios/Extensions/UIImage.swift b/unstoppable-ios-app/domains-manager-ios/Extensions/UIImage.swift index bc831762b..d03b65bd8 100644 --- a/unstoppable-ios-app/domains-manager-ios/Extensions/UIImage.swift +++ b/unstoppable-ios-app/domains-manager-ios/Extensions/UIImage.swift @@ -145,7 +145,6 @@ extension UIImage { static let cellChevron = UIImage(named: "cellChevron")! static let udCartLogoRaster = UIImage(named: "udCartLogoRaster")! static let chooseRRDomainIllustration = UIImage(named: "chooseRRDomainIllustration")! - static let udBlueGrayIcon = UIImage(named: "udBlueGrayIcon")! static let check = UIImage(named: "check")! static let statsIcon = UIImage(named: "statsIcon")! static let sparkleIcon = UIImage(named: "sparkleIcon")! diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/DomainProfile/Cells/TopInfoCell/DomainProfileTopInfoCell/DomainProfileTopInfoCell.swift b/unstoppable-ios-app/domains-manager-ios/Modules/DomainProfile/Cells/TopInfoCell/DomainProfileTopInfoCell/DomainProfileTopInfoCell.swift index 7ce86c9e3..2fb8d1931 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/DomainProfile/Cells/TopInfoCell/DomainProfileTopInfoCell/DomainProfileTopInfoCell.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/DomainProfile/Cells/TopInfoCell/DomainProfileTopInfoCell/DomainProfileTopInfoCell.swift @@ -19,7 +19,6 @@ final class DomainProfileTopInfoCell: BaseDomainProfileTopInfoCell { @IBOutlet private weak var domainNameLabel: UILabel! @IBOutlet private weak var qrCodeButton: SmallRaisedTertiaryWhiteButton! @IBOutlet private weak var followersButton: SmallRaisedTertiaryWhiteButton! - @IBOutlet private weak var udBlueImageView: UIImageView! private let dropItemIdentifier = UTType.image.identifier @@ -50,7 +49,6 @@ final class DomainProfileTopInfoCell: BaseDomainProfileTopInfoCell { lineBreakMode: .byTruncatingTail) followersButton.isHidden = false - udBlueImageView.isHidden = !data.isUDBlue let social = data.social let havingFollowers = social.followerCount > 0 let havingFollowings = social.followingCount > 0 diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/DomainProfile/Cells/TopInfoCell/DomainProfileTopInfoCell/DomainProfileTopInfoCell.xib b/unstoppable-ios-app/domains-manager-ios/Modules/DomainProfile/Cells/TopInfoCell/DomainProfileTopInfoCell/DomainProfileTopInfoCell.xib index 724c74f73..dc770980b 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/DomainProfile/Cells/TopInfoCell/DomainProfileTopInfoCell/DomainProfileTopInfoCell.xib +++ b/unstoppable-ios-app/domains-manager-ios/Modules/DomainProfile/Cells/TopInfoCell/DomainProfileTopInfoCell/DomainProfileTopInfoCell.xib @@ -1,9 +1,9 @@ - + - + @@ -72,21 +72,12 @@ - - - - - - - - - @@ -188,13 +179,11 @@ - - diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/DomainProfile/Entities/DomainProfileTopInfoData.swift b/unstoppable-ios-app/domains-manager-ios/Modules/DomainProfile/Entities/DomainProfileTopInfoData.swift index 68b8d4ffd..3eaa1b67a 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/DomainProfile/Entities/DomainProfileTopInfoData.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/DomainProfile/Entities/DomainProfileTopInfoData.swift @@ -13,12 +13,10 @@ struct DomainProfileTopInfoData { var imagePathPublic: Bool var coverPathPublic: Bool let social: DomainProfileSocialInfo - let isUDBlue: Bool init(profile: SerializedUserDomainProfile) { imagePathPublic = profile.profile.imagePathPublic coverPathPublic = profile.profile.coverPathPublic - isUDBlue = profile.profile.udBlue ?? false social = profile.social if let avatarPath = profile.profile.imagePath, diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/DomainProfile/Entities/DomainProfileViewControllerItemsEntities.swift b/unstoppable-ios-app/domains-manager-ios/Modules/DomainProfile/Entities/DomainProfileViewControllerItemsEntities.swift index 2bd053a5e..969e23b57 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/DomainProfile/Entities/DomainProfileViewControllerItemsEntities.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/DomainProfile/Entities/DomainProfileViewControllerItemsEntities.swift @@ -29,7 +29,6 @@ extension DomainProfileViewController { let id: UUID let domain: DomainDisplayInfo let social: DomainProfileSocialInfo - let isUDBlue: Bool let isEnabled: Bool let avatarImageState: DomainProfileTopInfoData.ImageState let bannerImageState: DomainProfileTopInfoData.ImageState diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/DomainProfile/Public Profile/Public profile view/PublicProfileView.swift b/unstoppable-ios-app/domains-manager-ios/Modules/DomainProfile/Public Profile/Public profile view/PublicProfileView.swift index b1c11bf7d..b7dd5553c 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/DomainProfile/Public Profile/Public profile view/PublicProfileView.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/DomainProfile/Public Profile/Public profile view/PublicProfileView.swift @@ -189,11 +189,6 @@ private extension PublicProfileView { height: avatarSize) .squareFrame(avatarSize) .clipForAvatarStyle(avatarStyle) - if viewModel.isUDBlue { - Image.udBlueGrayIcon - .resizable() - .squareFrame(24) - } } } diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/DomainProfile/Public Profile/Public profile view/PublicProfileViewModel.swift b/unstoppable-ios-app/domains-manager-ios/Modules/DomainProfile/Public Profile/Public profile view/PublicProfileViewModel.swift index ffeda705c..785259765 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/DomainProfile/Public Profile/Public profile view/PublicProfileViewModel.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/DomainProfile/Public Profile/Public profile view/PublicProfileViewModel.swift @@ -42,7 +42,6 @@ extension PublicProfileView { @Published var socialAccounts: SocialAccounts? @Published var error: Error? @Published private(set) var isLoading = false - @Published private(set) var isUDBlue = false @Published private(set) var isUserDomainSelected = true @Published private(set) var profile: SerializedPublicDomainProfile? @Published private(set) var badgesDisplayInfo: [DomainProfileBadgeDisplayInfo]? @@ -165,7 +164,6 @@ extension PublicProfileView { records = await convertRecordsFrom(recordsDict: profile.records ?? [:]) socialInfo = profile.social socialAccounts = profile.socialAccounts - isUDBlue = profile.profile.udBlue ?? false isLoading = false loadImages() } @@ -300,8 +298,7 @@ extension PublicDomainProfileAttributes { imageType: nil, coverPath: nil, phoneNumber: nil, - domainPurchased: nil, - udBlue: false) + domainPurchased: nil) static let filled = PublicDomainProfileAttributes(displayName: "Oleg Kuplin", description: "Unstoppable iOS developer", @@ -311,8 +308,7 @@ extension PublicDomainProfileAttributes { imageType: .onChain, coverPath: "nil", phoneNumber: nil, - domainPurchased: nil, - udBlue: false) + domainPurchased: nil) } func loadImageFrom(url: URL) async -> UIImage? { diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/DomainProfile/Sections/DomainProfileTopInfoSection.swift b/unstoppable-ios-app/domains-manager-ios/Modules/DomainProfile/Sections/DomainProfileTopInfoSection.swift index 4aff02e44..41183c5a0 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/DomainProfile/Sections/DomainProfileTopInfoSection.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/DomainProfile/Sections/DomainProfileTopInfoSection.swift @@ -34,7 +34,6 @@ extension DomainProfileTopInfoSection: DomainProfileSection { func fill(snapshot: inout DomainProfileSnapshot, withGeneralData generalData: DomainProfileGeneralData) { let domain = generalData.domain let social = topInfoData.social - let isUDBlue = topInfoData.isUDBlue let isEnabled = state == .default || state == .updatingRecords snapshot.appendSections([.topInfo]) @@ -42,7 +41,6 @@ extension DomainProfileTopInfoSection: DomainProfileSection { let itemData = DomainProfileViewController.ItemTopInfoData(id: id, domain: domain, social: social, - isUDBlue: isUDBlue, isEnabled: isEnabled, avatarImageState: editingTopInfoData.avatarImageState, bannerImageState: editingTopInfoData.bannerImageState, diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Chat/ChatView/ChatView.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Chat/ChatView/ChatView.swift index 18447d5ec..3c1571830 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Chat/ChatView/ChatView.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Chat/ChatView/ChatView.swift @@ -29,7 +29,6 @@ struct ChatView: View, ViewAnalyticsLogger { ProgressView() } } - .environmentObject(viewModel) .displayError($viewModel.error) .background(Color.backgroundMuted2) .onChange(of: viewModel.keyboardFocused) { keyboardFocused in @@ -55,6 +54,9 @@ struct ChatView: View, ViewAnalyticsLogger { .frame(maxWidth: .infinity) } } + .environmentObject(viewModel) + .environment(\.analyticsViewName, analyticsName) + .environment(\.analyticsAdditionalProperties, additionalAppearAnalyticParameters) .onAppear(perform: onAppear) } } diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Chat/ChatView/ChatViewModel.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Chat/ChatView/ChatViewModel.swift index d06779845..7549b5555 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Chat/ChatView/ChatViewModel.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Chat/ChatView/ChatViewModel.swift @@ -66,7 +66,7 @@ final class ChatViewModel: ObservableObject, ViewAnalyticsLogger { chatState = .loading setupTitle() setupPlaceholder() - setupFunctionality() + setIfUserCanSendAttachments() loadAndShowData() setListOfGroupParticipants() } @@ -356,9 +356,13 @@ private extension ChatViewModel { } } - func setupFunctionality() { + func setIfUserCanSendAttachments() { + let isProfileHasDomain = appContext.walletsDataService.wallets.findWithAddress(profile.wallet)?.rrDomain != nil if isCommunityChat() { - canSendAttachments = featureFlagsService.valueFor(flag: .communityMediaEnabled) + let isCommunityMediaEnabled = featureFlagsService.valueFor(flag: .communityMediaEnabled) + canSendAttachments = isCommunityMediaEnabled && isProfileHasDomain + } else { + canSendAttachments = isProfileHasDomain } } @@ -1020,7 +1024,7 @@ extension ChatViewModel: UDFeatureFlagsListener { switch flag { case .communityMediaEnabled: if isCommunityChat() { - canSendAttachments = newValue + setIfUserCanSendAttachments() reloadCachedMessages() } default: diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Chat/ChatView/InputView/ExpandableTextEditor.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Chat/ChatView/InputView/ExpandableTextEditor.swift index 5498ad172..8393e8840 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Chat/ChatView/InputView/ExpandableTextEditor.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Chat/ChatView/InputView/ExpandableTextEditor.swift @@ -33,7 +33,7 @@ struct ExpandableTextEditor: View { .foregroundStyle(Color.foregroundDefault) .frame(height: max(40, textEditorHeight)) .scrollContentBackground(.hidden) - .padding(EdgeInsets(top: 0, leading: 8, + .padding(EdgeInsets(top: 4, leading: 8, bottom: 0, trailing: 8)) .background(focused ? Color.backgroundMuted : Color.backgroundSubtle) .tint(Color.foregroundAccent) diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Chat/ChatView/InputView/MessageInputView.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Chat/ChatView/InputView/MessageInputView.swift index 70f6f1598..1f2be96c8 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Chat/ChatView/InputView/MessageInputView.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/Chat/ChatView/InputView/MessageInputView.swift @@ -7,8 +7,12 @@ import SwiftUI -struct MessageInputView: View { +struct MessageInputView: View, ViewAnalyticsLogger { + @Environment(\.analyticsViewName) var analyticsName + @Environment(\.analyticsAdditionalProperties) var additionalAppearAnalyticParameters + @EnvironmentObject var viewModel: ChatViewModel + let input: Binding let placeholder: String @FocusState.Binding var focused: Bool @@ -17,30 +21,13 @@ struct MessageInputView: View { var body: some View { HStack(alignment: .bottom, spacing: 16) { - Menu { - ForEach(AdditionalAction.allCases.filter { $0.isAvailable }, id: \.self) { action in - Button { - UDVibration.buttonTap.vibrate() - additionalActionCallback(action) - } label: { - Label(action.title, systemImage: action.icon) - } - } - } label: { - Image.plusIcon18 - .resizable() - .squareFrame(20) - .padding(EdgeInsets(10)) - .foregroundStyle(Color.foregroundSecondary) - } - .onButtonTap { -// logButtonPressedAnalyticEvents(button: action.analyticButton) + if viewModel.canSendAttachments { + additionalActionsView() } - textFieldView() - if !input.wrappedValue.isEmpty { Button { + UDVibration.buttonTap.vibrate() sendCallback() } label: { Image.arrowUp24 @@ -88,11 +75,45 @@ struct MessageInputView: View { return UnstoppableImagePicker.isCameraAvailable } } + + var analyticButton: Analytics.Button { + switch self { + case .takePhoto: + return .takePhoto + case .choosePhoto: + return .choosePhoto + } + } } } // MARK: - Private methods private extension MessageInputView { + @MainActor + @ViewBuilder + func additionalActionsView() -> some View { + Menu { + ForEach(AdditionalAction.allCases.filter { $0.isAvailable }, id: \.self) { action in + Button { + UDVibration.buttonTap.vibrate() + logButtonPressedAnalyticEvents(button: action.analyticButton) + additionalActionCallback(action) + } label: { + Label(action.title, systemImage: action.icon) + } + } + } label: { + Image.plusIcon18 + .resizable() + .squareFrame(20) + .padding(EdgeInsets(10)) + .foregroundStyle(Color.foregroundSecondary) + } + .onButtonTap { + logButtonPressedAnalyticEvents(button: .chatInputActions) + } + } + @ViewBuilder func textFieldView() -> some View { ExpandableTextEditor(text: input, diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/ChatsList/ChatListRows/ChatListChatRowView.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/ChatsList/ChatListRows/ChatListChatRowView.swift index 69b3d8d9a..08335385e 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/ChatsList/ChatListRows/ChatListChatRowView.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/ChatsList/ChatListRows/ChatListChatRowView.swift @@ -112,14 +112,28 @@ private extension ChatListChatRowView { @ViewBuilder func subtitleView() -> some View { - if let lastMessage = chat.lastMessage { - Text(lastMessageTextFrom(message: lastMessage)) + if let subtitle = getSubtitleText() { + Text(subtitle) .lineLimit(2) .foregroundStyle(Color.foregroundSecondary) .font(.currentFont(size: 14)) } } + func getSubtitleText() -> String? { + if let lastMessage = chat.lastMessage { + return lastMessageTextFrom(message: lastMessage) + } else if case .community(let details) = chat.type { + switch details.type { + case .badge(let badgeDetailedInfo): + let holders = badgeDetailedInfo.usage.holders + let holdersKsString = holders.asFormattedKsString + return String.Constants.pluralNHolders.localized(holdersKsString, holders) + } + } + return nil + } + func lastMessageTextFrom(message: MessagingChatMessageDisplayInfo) -> String { switch message.type { case .text(let description): diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/ChatsList/ChatListViews/ChatListNavTitleView.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/ChatsList/ChatListViews/ChatListNavTitleView.swift index fcd73fbe1..a0c89985c 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/ChatsList/ChatListViews/ChatListNavTitleView.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/ChatsList/ChatListViews/ChatListNavTitleView.swift @@ -20,13 +20,20 @@ struct ChatListNavTitleView: View { UDVibration.buttonTap.vibrate() tabRouter.isSelectProfilePresented = true } label: { - HStack { - content() - if selectable { - Image.chevronDown - .resizable() - .squareFrame(20) - .foregroundStyle(Color.foregroundDefault) + ZStack { + Rectangle() + .frame(width: 200, + height: 30) + .opacity(0.001) + .layoutPriority(-1) + HStack { + content() + if selectable { + Image.chevronDown + .resizable() + .squareFrame(20) + .foregroundStyle(Color.foregroundDefault) + } } } } @@ -49,6 +56,7 @@ private extension ChatListNavTitleView { var selectable: Bool { appContext.userProfileService.profiles.count > 1 } + @ViewBuilder func content() -> some View { switch profile { @@ -122,3 +130,7 @@ private extension ChatListNavTitleView { extension ChatListNavTitleView { } + +#Preview { + ChatListNavTitleView(profile: appContext.userProfileService.selectedProfile!) +} diff --git a/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/ChatsList/SelectableChatRowView.swift b/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/ChatsList/SelectableChatRowView.swift index d09fb6118..0c1b52a95 100644 --- a/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/ChatsList/SelectableChatRowView.swift +++ b/unstoppable-ios-app/domains-manager-ios/Modules/Messaging/ChatsList/SelectableChatRowView.swift @@ -26,6 +26,7 @@ struct SelectableChatRowView: View, ViewAnalyticsLogger { ChatListChatRowView(chat: chat, joinCommunityCallback: { joinCommunityCallback?(chat) }) + .padding(.init(horizontal: 12)) } } } diff --git a/unstoppable-ios-app/domains-manager-ios/NetworkEnvironment/ApiRequestBuilder.swift b/unstoppable-ios-app/domains-manager-ios/NetworkEnvironment/ApiRequestBuilder.swift index 7b3dee4d6..385445673 100644 --- a/unstoppable-ios-app/domains-manager-ios/NetworkEnvironment/ApiRequestBuilder.swift +++ b/unstoppable-ios-app/domains-manager-ios/NetworkEnvironment/ApiRequestBuilder.swift @@ -836,6 +836,25 @@ extension Endpoint { ) } + static func uploadRemoteAttachment(for domain: DomainItem, + with timedSignature: PersistedTimedSignature, + body: String) throws -> Endpoint { + // https://profile.ud-staging.com/api/user/aaron.x/attachment + let expires = "\(timedSignature.expires)" + let signature = timedSignature.sign + let headers = [ + SignatureComponentHeaders.CodingKeys.domain.rawValue: domain.name, + SignatureComponentHeaders.CodingKeys.expires.rawValue: expires, + SignatureComponentHeaders.CodingKeys.signature.rawValue: signature + ] + return Endpoint( + host: NetworkConfig.baseProfileHost, + path: "/profile/user/\(domain.name)/attachment", + queryItems: [], + body: body, + headers: headers + ) + } } // MARK: - Open methods diff --git a/unstoppable-ios-app/domains-manager-ios/Services/AnalyticsService/AnalyticsServiceEnvironment.swift b/unstoppable-ios-app/domains-manager-ios/Services/AnalyticsService/AnalyticsServiceEnvironment.swift index 6b7086bbb..baadb9098 100644 --- a/unstoppable-ios-app/domains-manager-ios/Services/AnalyticsService/AnalyticsServiceEnvironment.swift +++ b/unstoppable-ios-app/domains-manager-ios/Services/AnalyticsService/AnalyticsServiceEnvironment.swift @@ -345,6 +345,7 @@ extension Analytics { case copyChatMessageToClipboard case saveChatImage case blockUserInGroupChat + case chatInputActions, takePhoto, choosePhoto // Public profile case follow, unfollow diff --git a/unstoppable-ios-app/domains-manager-ios/Services/FeatureFlags/UDFeatureFlag.swift b/unstoppable-ios-app/domains-manager-ios/Services/FeatureFlags/UDFeatureFlag.swift index 2babb5dc0..1e81d037b 100644 --- a/unstoppable-ios-app/domains-manager-ios/Services/FeatureFlags/UDFeatureFlag.swift +++ b/unstoppable-ios-app/domains-manager-ios/Services/FeatureFlags/UDFeatureFlag.swift @@ -9,11 +9,10 @@ import Foundation enum UDFeatureFlag: String, CaseIterable { case communityMediaEnabled = "ecommerce-service-users-enable-chat-community-media" - case udBlueRequiredForCommunities = "ecommerce-service-users-enable-chat-community-udBlue" var defaultValue: Bool { switch self { - case .communityMediaEnabled, .udBlueRequiredForCommunities: + case .communityMediaEnabled: return false } } diff --git a/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/SubServices/MessagingAPI/MessagingAPIServiceHelper.swift b/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/SubServices/MessagingAPI/MessagingAPIServiceHelper.swift index 2c8bff03c..376abfaed 100644 --- a/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/SubServices/MessagingAPI/MessagingAPIServiceHelper.swift +++ b/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/SubServices/MessagingAPI/MessagingAPIServiceHelper.swift @@ -44,6 +44,17 @@ struct MessagingAPIServiceHelper { return serviceMetadata } + static func uploadDataToWeb3Storage(_ data: Data, + ofType type: String, + by wallet: HexAddress) async throws -> URL { + let domain = try await MessagingAPIServiceHelper.getAnyDomainItem(for: wallet) + let response = try await NetworkService().uploadRemoteAttachment(for: domain, + base64: data.base64EncodedString(), + type: type) + return response.url + } + + enum MessagingHelperError: String, LocalizedError { case noDomainForWallet case failedToDecodeServiceData diff --git a/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/SubServices/MessagingAPI/PushMessagingAPIService.swift b/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/SubServices/MessagingAPI/PushMessagingAPIService.swift index f844bbc83..a67242fb5 100644 --- a/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/SubServices/MessagingAPI/PushMessagingAPIService.swift +++ b/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/SubServices/MessagingAPI/PushMessagingAPIService.swift @@ -645,7 +645,8 @@ private extension PushMessagingAPIService { receiver: String, by user: MessagingChatUserProfile) async throws -> Push.PushChat.SendOptions { let env = getCurrentPushEnvironment() - let pushMessageContent = try getPushMessageContentFrom(displayType: messageType) + let pushMessageContent = try await getPushMessageContentFrom(displayType: messageType, + by: user) let pushMessageType = try getPushMessageTypeFrom(displayType: messageType) let pgpPrivateKey = try await getPGPPrivateKeyFor(user: user) let reference = getPushMessageReferenceFrom(displayType: messageType) @@ -668,19 +669,16 @@ private extension PushMessagingAPIService { try MessagingAPIServiceHelper.decodeServiceMetadata(from: data) } - func getPushMessageContentFrom(displayType: MessagingChatMessageDisplayType) throws -> String { + func getPushMessageContentFrom(displayType: MessagingChatMessageDisplayType, + by user: MessagingChatUserProfile) async throws -> String { switch displayType { case .text(let details): return details.text case .imageBase64(let details): - let entity = PushEnvironment.PushMessageContentResponse(content: details.base64) - guard let jsonString = entity.jsonString() else { throw PushMessagingAPIServiceError.failedToPrepareMessageContent } - return jsonString + guard let data = Data(base64Encoded: details.base64) else { throw PushMessagingAPIServiceError.failedToPrepareMessageContent } + return try await getImagePushMessageContentFrom(data: data, by: user) case .imageData(let details): - guard let base64 = details.image?.base64String else { throw PushMessagingAPIServiceError.unsupportedType } - let preparedBase64 = Base64DataTransformer.addingImageIdentifier(to: base64) - let imageBase64TypeDetails = MessagingChatMessageImageBase64TypeDisplayInfo(base64: preparedBase64) - return try getPushMessageContentFrom(displayType: .imageBase64(imageBase64TypeDetails)) + return try await getImagePushMessageContentFrom(data: details.data, by: user) case .reaction(let details): return details.content case .unknown, .remoteContent: @@ -688,6 +686,15 @@ private extension PushMessagingAPIService { } } + func getImagePushMessageContentFrom(data: Data, + by user: MessagingChatUserProfile) async throws -> String { + let wallet = user.wallet + let url = try await MessagingAPIServiceHelper.uploadDataToWeb3Storage(data, + ofType: "image/png", + by: wallet) + return url.absoluteString + } + func getPushMessageReferenceFrom(displayType: MessagingChatMessageDisplayType) -> String? { switch displayType { case .reaction(let details): @@ -704,7 +711,7 @@ private extension PushMessagingAPIService { case .reaction: return .reaction case .imageBase64, .imageData: - return .image + return .mediaEmbed case .unknown, .remoteContent: throw PushMessagingAPIServiceError.unsupportedType } diff --git a/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/SubServices/MessagingAPI/XMTPMessagingAPIService.swift b/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/SubServices/MessagingAPI/XMTPMessagingAPIService.swift index 5fd770588..ebd8ad48a 100644 --- a/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/SubServices/MessagingAPI/XMTPMessagingAPIService.swift +++ b/unstoppable-ios-app/domains-manager-ios/Services/MessagingService/SubServices/MessagingAPI/XMTPMessagingAPIService.swift @@ -22,7 +22,6 @@ final class XMTPMessagingAPIService { private let blockedUsersStorage = XMTPBlockedUsersStorage.shared private let approvedUsersStorage = XMTPApprovedTopicsStorage.shared - private let cachedDataHolder = CachedDataHolder() let capabilities = MessagingServiceCapabilities(canContactWithoutProfile: false, canBlockUsers: true, isSupportChatsListPagination: false, @@ -530,70 +529,19 @@ private extension XMTPMessagingAPIService { let encryptedAttachment = try RemoteAttachment.encodeEncrypted(content: attachment, codec: AttachmentCodec(), with: client) - let url = try await uploadDataToWeb3Storage(encryptedAttachment.payload, by: wallet) - let remoteAttachment = try RemoteAttachment(url: url, + let url = try await MessagingAPIServiceHelper.uploadDataToWeb3Storage(encryptedAttachment.payload, + ofType: mimeType, + by: wallet) + let urlString = url.absoluteString + let remoteAttachment = try RemoteAttachment(url: urlString, encryptedEncodedContent: encryptedAttachment) return try await conversation.send(content: remoteAttachment, options: .init(contentType: ContentTypeRemoteAttachment)) } - - func uploadDataToWeb3Storage(_ data: Data, - by wallet: HexAddress) async throws -> String { - struct Web3StorageResponse: Codable { - let carCid: String - let cid: String - } - - let token = await getWeb3StorageKey(for: wallet) - let url = URL(string: "https://api.web3.storage/upload")! - var request = URLRequest(url: url) - request.addValue("Bearer \(token)", forHTTPHeaderField: "Authorization") - request.addValue("XMTP", forHTTPHeaderField: "X-NAME") - request.httpMethod = "POST" - - let responseData = try await URLSession.shared.upload(for: request, from: data).0 - let response = try Web3StorageResponse.objectFromDataThrowing(responseData) - - return "https://\(response.cid).ipfs.w3s.link" - } - + func getXMTPConversationTopicFromChat(_ chat: MessagingChat) -> String { chat.displayInfo.id // XMTP Chat's topic = chat's id } - - func getWeb3StorageKey(for wallet: HexAddress) async -> String { - if let cachedKey = await cachedDataHolder.getKeyFor(wallet: wallet) { - return cachedKey - } else if let domain = try? await MessagingAPIServiceHelper.getAnyDomainItem(for: wallet), - let profile = try? await NetworkService().fetchUserDomainProfile(for: domain, fields: [.profile]), - let storage = profile.storage, - storage.type == .web3 { - let key = storage.apiKey - await cachedDataHolder.setKey(key, for: wallet) - return key - } - - if User.instance.getSettings().isTestnetUsed { - return Web3Storage.StagingAPIKey - } else { - return Web3Storage.ProductionAPIKey - } - } -} - -// MARK: - Private methods -private extension XMTPMessagingAPIService { - actor CachedDataHolder { - var walletsToStorageKeys: [HexAddress : String] = [:] - - func getKeyFor(wallet: HexAddress) -> String? { - walletsToStorageKeys[wallet] - } - - func setKey(_ key: String, for wallet: HexAddress) { - walletsToStorageKeys[wallet] = key - } - } } // MARK: - Open methods diff --git a/unstoppable-ios-app/domains-manager-ios/Services/Networking/NetworkService+ProfilesApi.swift b/unstoppable-ios-app/domains-manager-ios/Services/Networking/NetworkService+ProfilesApi.swift index f5f28cd08..1b8a852a2 100644 --- a/unstoppable-ios-app/domains-manager-ios/Services/Networking/NetworkService+ProfilesApi.swift +++ b/unstoppable-ios-app/domains-manager-ios/Services/Networking/NetworkService+ProfilesApi.swift @@ -145,6 +145,21 @@ extension NetworkService { return try await updateUserDomainProfile(for: domain, body: body) } + @discardableResult + public func uploadRemoteAttachment(for domain: DomainItem, + base64: String, + type: String) async throws -> ProfileUploadRemoteAttachmentResponse { + let request = ProfileUploadRemoteAttachmentRequest(base64: base64, type: type) + let body = try prepareRequestBodyFrom(entity: request) + let persistedSignature = try await getOrCreateAndStorePersistedProfileSignature(for: domain) + let endpoint = try Endpoint.uploadRemoteAttachment(for: domain, + with: persistedSignature, + body: body) + let data = try await fetchDataHandlingThrottleFor(endpoint: endpoint, method: .post) + let info = try ProfileUploadRemoteAttachmentResponse.objectFromDataThrowing(data) + return info + } + public func updatePendingDomainProfiles(with requests: [UpdateProfilePendingChangesRequest]) async throws { try await withThrowingTaskGroup(of: Void.self) { group in for request in requests { diff --git a/unstoppable-ios-app/domains-manager-ios/Services/Networking/NetworkService+ProfilesEntities.swift b/unstoppable-ios-app/domains-manager-ios/Services/Networking/NetworkService+ProfilesEntities.swift index 6f811da3b..c1b81e42b 100644 --- a/unstoppable-ios-app/domains-manager-ios/Services/Networking/NetworkService+ProfilesEntities.swift +++ b/unstoppable-ios-app/domains-manager-ios/Services/Networking/NetworkService+ProfilesEntities.swift @@ -94,7 +94,6 @@ struct PublicDomainProfileAttributes: Decodable { let coverPath: String? let phoneNumber: String? let domainPurchased: Bool? - let udBlue: Bool? enum CodingKeys: CodingKey { case displayName @@ -106,7 +105,6 @@ struct PublicDomainProfileAttributes: Decodable { case coverPath case phoneNumber case domainPurchased - case udBlue } } @@ -128,7 +126,6 @@ extension PublicDomainProfileAttributes { self.imageType = nil } self.domainPurchased = try? container.decode(Bool.self, forKey: .domainPurchased) - self.udBlue = try? container.decode(Bool.self, forKey: .udBlue) } } @@ -157,7 +154,6 @@ struct UserDomainProfileAttributes: Codable { let imagePathPublic: Bool let coverPathPublic: Bool let phoneNumberPublic: Bool - let udBlue: Bool? enum CodingKeys: CodingKey { case id @@ -179,7 +175,6 @@ struct UserDomainProfileAttributes: Codable { case coverPathPublic case web2UrlPublic case phoneNumberPublic - case udBlue } internal init(id: UInt = 0, @@ -200,8 +195,7 @@ struct UserDomainProfileAttributes: Codable { imagePathPublic: Bool = false, coverPathPublic: Bool = false, web2UrlPublic: Bool = false, - phoneNumberPublic: Bool = false, - udBlue: Bool = false) { + phoneNumberPublic: Bool = false) { self.id = id self.domainId = domainId self.privateEmail = privateEmail @@ -221,7 +215,6 @@ struct UserDomainProfileAttributes: Codable { self.coverPathPublic = coverPathPublic self.web2UrlPublic = web2UrlPublic self.phoneNumberPublic = phoneNumberPublic - self.udBlue = udBlue } init(from decoder: Decoder) throws { @@ -250,7 +243,6 @@ struct UserDomainProfileAttributes: Codable { self.coverPathPublic = try container.decode(Bool.self, forKey: UserDomainProfileAttributes.CodingKeys.coverPathPublic) self.web2UrlPublic = try container.decode(Bool.self, forKey: UserDomainProfileAttributes.CodingKeys.web2UrlPublic) self.phoneNumberPublic = try container.decode(Bool.self, forKey: UserDomainProfileAttributes.CodingKeys.phoneNumberPublic) - self.udBlue = try? container.decode(Bool.self, forKey: UserDomainProfileAttributes.CodingKeys.udBlue) } func encode(to encoder: Encoder) throws { @@ -275,7 +267,6 @@ struct UserDomainProfileAttributes: Codable { try container.encode(coverPathPublic, forKey: .coverPathPublic) try container.encode(web2UrlPublic, forKey: .web2UrlPublic) try container.encode(phoneNumberPublic, forKey: .phoneNumberPublic) - try container.encode(udBlue, forKey: .udBlue) } } @@ -410,8 +401,8 @@ struct UserDomainNotificationsPreferences: Codable { } struct UserDomainStorageDetails: Codable { - let apiKey: String - let type: StorageType + let apiKey: String? + let type: StorageType? enum StorageType: String, Codable { case web3 = "web3.storage" @@ -701,3 +692,23 @@ struct WalletTokenPortfolio: Codable, Hashable { let value: WalletTokenPortfolio.Value? } } + +// MARK: Profile Update Request Structures +struct ProfileUploadRemoteAttachmentRequest: Codable { + let attachment: Attachment + + init(base64: String, + type: String) { + self.attachment = .init(base64: base64, type: type) + } + + struct Attachment: Codable { + let base64: String + let type: String + } +} + +// MARK: Profile Update Request Structures +struct ProfileUploadRemoteAttachmentResponse: Codable { + let url: URL +} diff --git a/unstoppable-ios-app/domains-manager-ios/Services/Networking/NetworkService.swift b/unstoppable-ios-app/domains-manager-ios/Services/Networking/NetworkService.swift index 25297cc4d..40ef31dc6 100644 --- a/unstoppable-ios-app/domains-manager-ios/Services/Networking/NetworkService.swift +++ b/unstoppable-ios-app/domains-manager-ios/Services/Networking/NetworkService.swift @@ -119,16 +119,16 @@ struct NetworkService { @discardableResult func fetchDataHandlingThrottleFor(endpoint: Endpoint, - method: HttpRequestMethod) async throws -> Data { + method: HttpRequestMethod) async throws -> Data { guard let url = endpoint.url else { throw NetworkLayerError.creatingURLFailed } let data = try await fetchDataHandlingThrottle(for: url, body: endpoint.body, method: method, extraHeaders: endpoint.headers) return data } func fetchDataHandlingThrottle(for url: URL, - body: String = "", - method: HttpRequestMethod = .post, - extraHeaders: [String: String] = [:]) async throws -> Data { + body: String = "", + method: HttpRequestMethod = .post, + extraHeaders: [String: String] = [:]) async throws -> Data { let data: Data do { data = try await fetchData(for: url, body: body, method: method, extraHeaders: extraHeaders) diff --git a/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/DomainProfile/udBlueGrayIcon.imageset/Contents.json b/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/DomainProfile/udBlueGrayIcon.imageset/Contents.json deleted file mode 100644 index 19c80e07d..000000000 --- a/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/DomainProfile/udBlueGrayIcon.imageset/Contents.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "images" : [ - { - "filename" : "udBlueBlueIcon.png", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "original" - } -} diff --git a/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/DomainProfile/udBlueGrayIcon.imageset/udBlueBlueIcon.png b/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/DomainProfile/udBlueGrayIcon.imageset/udBlueBlueIcon.png deleted file mode 100644 index 973b1450c..000000000 Binary files a/unstoppable-ios-app/domains-manager-ios/SupportingFiles/Assets.xcassets/DomainProfile/udBlueGrayIcon.imageset/udBlueBlueIcon.png and /dev/null differ diff --git a/unstoppable-ios-app/domains-manager-ios/SwiftUI/Extensions/Image.swift b/unstoppable-ios-app/domains-manager-ios/SwiftUI/Extensions/Image.swift index 761321fce..b0d61a75b 100644 --- a/unstoppable-ios-app/domains-manager-ios/SwiftUI/Extensions/Image.swift +++ b/unstoppable-ios-app/domains-manager-ios/SwiftUI/Extensions/Image.swift @@ -23,7 +23,6 @@ extension Image { static let chevronDown = Image(uiImage: .chevronDown) static let checkCircle = Image(uiImage: .checkCircle) static let udCartLogoRaster = Image(uiImage: .udCartLogoRaster) - static let udBlueGrayIcon = Image(uiImage: .udBlueGrayIcon) static let crossWhite = Image(uiImage: .crossWhite) static let chevronRight = Image(uiImage: .chevronRight) static let check = Image(uiImage: .check)