From abb8fc84e9fc4f3c2498c90f4ecb92b5252df5e9 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Mon, 11 Aug 2025 16:56:46 +0530 Subject: [PATCH] (IOS) Consistent component for upload item for share extension (#9030) Co-authored-by: Mattermost Build --- .../AttachmentsViews/AttachmentInfoView.swift | 33 ++++++++--- .../AttachmentsViews/AttachmentsView.swift | 19 ++++-- .../AttachmentsViews/FileThumbnail.swift | 36 +++++------ .../AttachmentsViews/ImageThumbnail.swift | 11 ++-- .../MultipleAttachmentView.swift | 59 ++++++------------- .../RemoveAttachmentView.swift | 30 +++++----- .../SingleAttachmentView.swift | 59 ++++++++++++++----- .../AttachmentsViews/VideoThumbnail.swift | 9 ++- .../Views/ContentViews/ContentView.swift | 6 +- 9 files changed, 141 insertions(+), 121 deletions(-) diff --git a/ios/MattermostShare/Views/ContentViews/AttachmentsViews/AttachmentInfoView.swift b/ios/MattermostShare/Views/ContentViews/AttachmentsViews/AttachmentInfoView.swift index d73fbaa9d..9a7c8cecf 100644 --- a/ios/MattermostShare/Views/ContentViews/AttachmentsViews/AttachmentInfoView.swift +++ b/ios/MattermostShare/Views/ContentViews/AttachmentsViews/AttachmentInfoView.swift @@ -11,32 +11,47 @@ import SwiftUI struct AttachmentInfoView: View { var attachment: AttachmentModel var hasError: Bool + var fullWidth: Bool = false var body: some View { - HStack { - attachment.icon() - VStack(alignment: .leading) { + HStack(spacing: 12) { + VStack { + attachment.icon() + } + .frame(width: 48, height: 48) + .background(Color.clear) + + VStack(alignment: .leading, spacing: 2) { Text(attachment.fileName) .foregroundColor(Color.theme.centerChannelColor) .lineLimit(1) .font(Font.custom("OpenSans-SemiBold", size: 16)) + .frame(maxWidth: .infinity, alignment: .leading) + Text("\(attachment.fileUrl.pathExtension.uppercased()) \(attachment.formattedFileSize)") .foregroundColor(Color.theme.centerChannelColor.opacity(0.64)) .lineLimit(1) .font(Font.custom("OpenSans", size: 12)) + .frame(maxWidth: .infinity, alignment: .leading) } + .frame(maxWidth: .infinity, alignment: .leading) + + Spacer(minLength: 0) } - .padding(.all, 12) - .frame(height: 64, alignment: .leading) - .frame(minWidth: 0, maxWidth: .infinity, maxHeight: 64, alignment: .leading) - .background( + .padding(.leading, 12) + .padding(.trailing, 16) + .padding(.vertical, 12) + .frame(height: 64) + .frame(maxWidth: fullWidth ? .infinity : 240) + .background(Color.theme.centerChannelBg) + .overlay( RoundedRectangle(cornerRadius: 4) .stroke( hasError ? Color.theme.errorTextColor : Color.theme.centerChannelColor.opacity(0.16), lineWidth: 1 ) - .shadow(color: Color(red: 0, green: 0, blue: 0, opacity: 0.08), radius: 3, x: 0, y: 2) - .background(.background) ) + .cornerRadius(4) + .shadow(color: Color(red: 0, green: 0, blue: 0, opacity: 0.08), radius: 3, x: 0, y: 2) } } diff --git a/ios/MattermostShare/Views/ContentViews/AttachmentsViews/AttachmentsView.swift b/ios/MattermostShare/Views/ContentViews/AttachmentsViews/AttachmentsView.swift index 4ec438106..7b48a8684 100644 --- a/ios/MattermostShare/Views/ContentViews/AttachmentsViews/AttachmentsView.swift +++ b/ios/MattermostShare/Views/ContentViews/AttachmentsViews/AttachmentsView.swift @@ -46,14 +46,20 @@ struct AttachmentsView: View { } var body: some View { - VStack (spacing: 8) { + VStack(alignment: .leading, spacing: 0) { if (attachments.count == 1) { + let attachment = attachments[0] + let isImageType = attachment.type == .image + SingleAttachmentView( - attachment: attachments[0], - hasError: attachments[0].sizeError(server: shareViewModel.server) || - attachments[0].resolutionError(server: shareViewModel.server) + attachments: $attachments, + attachment: attachment, + index: 0, + hasError: attachment.sizeError(server: shareViewModel.server) || + attachment.resolutionError(server: shareViewModel.server), + isSmall: isImageType ) - .padding(.horizontal, 20) + .padding(.horizontal, 16) .transition(.opacity) } else { MultipleAttachmentView(attachments: $attachments) @@ -62,7 +68,8 @@ struct AttachmentsView: View { if error != nil { ErrorLabelView(error: error!) - .padding(.horizontal, 20) + .padding(.horizontal, 16) + .padding(.top, 8) } } .animation(.linear(duration: 0.3)) diff --git a/ios/MattermostShare/Views/ContentViews/AttachmentsViews/FileThumbnail.swift b/ios/MattermostShare/Views/ContentViews/AttachmentsViews/FileThumbnail.swift index 4a7dce749..274475153 100644 --- a/ios/MattermostShare/Views/ContentViews/AttachmentsViews/FileThumbnail.swift +++ b/ios/MattermostShare/Views/ContentViews/AttachmentsViews/FileThumbnail.swift @@ -8,31 +8,25 @@ import SwiftUI struct FileThumbnail: View { + var small = false var attachment: AttachmentModel var hasError: Bool var body: some View { - VStack { - attachment.icon() - Text(attachment.fileName) - .foregroundColor(Color.theme.centerChannelColor) - .lineLimit(1) - .font(Font.custom("OpenSans-SemiBold", size: 10)) - .padding(.horizontal, 4) - Text("\(attachment.fileUrl.pathExtension.uppercased()) \(attachment.formattedFileSize)") - .foregroundColor(Color.theme.centerChannelColor.opacity(0.64)) - .lineLimit(1) - .font(Font.custom("OpenSans", size: 12)) + if small { + // Small mode: Use AttachmentInfoView with fixed width + AttachmentInfoView( + attachment: attachment, + hasError: hasError, + fullWidth: false + ) + } else { + // Full mode: Use AttachmentInfoView with full width + AttachmentInfoView( + attachment: attachment, + hasError: hasError, + fullWidth: true + ) } - .background( - RoundedRectangle(cornerRadius: 4) - .stroke( - hasError ? Color.theme.errorTextColor : Color.theme.centerChannelColor.opacity(0.16), - lineWidth: 1 - ) - .shadow(color: Color(red: 0, green: 0, blue: 0, opacity: 0.08), radius: 3, x: 0, y: 2) - .frame(width: 104, height: 104) - ) - .frame(width: 104, height: 104) } } diff --git a/ios/MattermostShare/Views/ContentViews/AttachmentsViews/ImageThumbnail.swift b/ios/MattermostShare/Views/ContentViews/AttachmentsViews/ImageThumbnail.swift index 3f4373131..edc2b9d9d 100644 --- a/ios/MattermostShare/Views/ContentViews/AttachmentsViews/ImageThumbnail.swift +++ b/ios/MattermostShare/Views/ContentViews/AttachmentsViews/ImageThumbnail.swift @@ -42,18 +42,21 @@ struct ImageThumbnail: View { } var body: some View { - Image(uiImage: downsample(imageAt: attachment.fileUrl, to: CGSize(width: small ? 104 : 0, height: small ? 104 : 156))!) + let imageSize: CGFloat = small ? 64 : 156 + + Image(uiImage: downsample(imageAt: attachment.fileUrl, to: CGSize(width: imageSize, height: imageSize))!) .resizable() .aspectRatio(contentMode: small ? .fill : .fit) - .frame(width: small ? 104 : nil, height: small ? 104 : 156) + .frame(width: small ? 64 : nil, height: small ? 64 : imageSize) + .clipped() .cornerRadius(4) - .background( + .background(Color.theme.centerChannelBg) + .overlay( RoundedRectangle(cornerRadius: 4) .stroke( hasError ? Color.theme.errorTextColor : Color.theme.centerChannelColor.opacity(0.16), lineWidth: 1 ) - .shadow(color: Color(red: 0, green: 0, blue: 0, opacity: 0.08), radius: 3, x: 0, y: 2) ) } } diff --git a/ios/MattermostShare/Views/ContentViews/AttachmentsViews/MultipleAttachmentView.swift b/ios/MattermostShare/Views/ContentViews/AttachmentsViews/MultipleAttachmentView.swift index 36ded952b..82a15e400 100644 --- a/ios/MattermostShare/Views/ContentViews/AttachmentsViews/MultipleAttachmentView.swift +++ b/ios/MattermostShare/Views/ContentViews/AttachmentsViews/MultipleAttachmentView.swift @@ -13,55 +13,29 @@ struct MultipleAttachmentView: View { @Binding var attachments: [AttachmentModel] var body: some View { - VStack (alignment: .leading) { + VStack (alignment: .leading, spacing: 4) { ScrollView (.horizontal, showsIndicators: true) { - HStack(spacing: 12) { + HStack(spacing: 8) { ForEach(attachments.indices, id: \.self) { index in let attachment = attachments[index] let hasError = attachment.sizeError(server: shareViewModel.server) || attachment.resolutionError(server: shareViewModel.server) let isFirst = index == 0 let isLast = index == attachments.count - 1 - VStack { - if attachment.type == .image { - AttachmentWrapperView(isFirst: isFirst, isLast: isLast) { - ImageThumbnail( - small: true, - attachment: attachment, - hasError: hasError - ) - .overlay( - RemoveAttachmentView(attachments: $attachments, index: index), - alignment: .topTrailing - ) - } - } else if attachment.type == .video { - AttachmentWrapperView(isFirst: isFirst, isLast: isLast) { - VideoThumbnail( - small: true, - attachment: attachment, - hasError: hasError - ) - .overlay( - RemoveAttachmentView(attachments: $attachments, index: index), - alignment: .topTrailing - ) - } - } else if attachment.type == .file { - AttachmentWrapperView(isFirst: isFirst, isLast: isLast) { - FileThumbnail( - attachment: attachment, - hasError: hasError - ) - .overlay( - RemoveAttachmentView(attachments: $attachments, index: index), - alignment: .topTrailing - ) - } - } - } + + SingleAttachmentView( + attachments: $attachments, + attachment: attachment, + index: index, + hasError: hasError, + isSmall: true + ) + .padding(.top, 8) + .padding(.trailing, 8) + .padding(.leading, isFirst ? 16 : 0) + .padding(.trailing, isLast ? 16 : 0) } } - .frame(height: 116) + .frame(height: 80) } if (attachments.count > 1) { @@ -74,7 +48,8 @@ struct MultipleAttachmentView: View { ) .foregroundColor(Color.theme.centerChannelColor.opacity(0.64)) .font(Font.custom("OpenSans", size: 12)) - .padding(.leading, 20) + .padding(.leading, 16) + .padding(.top, 4) } } } diff --git a/ios/MattermostShare/Views/ContentViews/AttachmentsViews/RemoveAttachmentView.swift b/ios/MattermostShare/Views/ContentViews/AttachmentsViews/RemoveAttachmentView.swift index 2f3dee314..1548fa9a8 100644 --- a/ios/MattermostShare/Views/ContentViews/AttachmentsViews/RemoveAttachmentView.swift +++ b/ios/MattermostShare/Views/ContentViews/AttachmentsViews/RemoveAttachmentView.swift @@ -17,20 +17,20 @@ struct RemoveAttachmentView: View { } var body: some View { - if attachments.count > 1 { - FontIcon.button( - .compassIcons(code: .closeCircle), - action: { - withAnimation { - removeAtIndex(index) - } - }, - fontsize: 24, - color: Color.theme.centerChannelColor.opacity(0.56) - ) - .background(.background) - .cornerRadius(12) - .offset(x: 5, y: -7) - } + // X button styled like Android RemoveButton + FontIcon.button( + .compassIcons(code: .closeCircle), + action: { + withAnimation { + removeAtIndex(index) + } + }, + fontsize: 24, + color: Color.theme.centerChannelColor.opacity(0.64) + ) + .frame(width: 24, height: 24) + .background(Color.theme.centerChannelBg) + .cornerRadius(12) + .shadow(color: Color(red: 0, green: 0, blue: 0, opacity: 0.08), radius: 2, x: 0, y: 1) } } diff --git a/ios/MattermostShare/Views/ContentViews/AttachmentsViews/SingleAttachmentView.swift b/ios/MattermostShare/Views/ContentViews/AttachmentsViews/SingleAttachmentView.swift index 0d3cc7c96..ec7fe7eb9 100644 --- a/ios/MattermostShare/Views/ContentViews/AttachmentsViews/SingleAttachmentView.swift +++ b/ios/MattermostShare/Views/ContentViews/AttachmentsViews/SingleAttachmentView.swift @@ -9,25 +9,54 @@ import SwiftUI struct SingleAttachmentView: View { + @Binding var attachments: [AttachmentModel] var attachment: AttachmentModel + var index: Int var hasError: Bool + var isSmall: Bool = false var body: some View { - if (attachment.type == .image) { - ImageThumbnail( - attachment: attachment, - hasError: hasError - ) - } else if (attachment.type == .video) { - VideoThumbnail( - attachment: attachment, - hasError: hasError - ) - } else { - AttachmentInfoView( - attachment: attachment, - hasError: hasError - ) + ZStack { + Group { + if attachment.type == .image { + ImageThumbnail( + small: isSmall, + attachment: attachment, + hasError: hasError + ) + } else if attachment.type == .video { + VideoThumbnail( + small: isSmall, + attachment: attachment, + hasError: hasError + ) + } else { + if isSmall { + FileThumbnail( + small: true, + attachment: attachment, + hasError: hasError + ) + } else { + AttachmentInfoView( + attachment: attachment, + hasError: hasError, + fullWidth: true + ) + } + } + } + + if isSmall && attachments.count > 1 { + VStack { + HStack { + Spacer() + RemoveAttachmentView(attachments: $attachments, index: index) + .offset(x: 8, y: -8) + } + Spacer() + } + } } } } diff --git a/ios/MattermostShare/Views/ContentViews/AttachmentsViews/VideoThumbnail.swift b/ios/MattermostShare/Views/ContentViews/AttachmentsViews/VideoThumbnail.swift index ceb1b7ce1..63c755aba 100644 --- a/ios/MattermostShare/Views/ContentViews/AttachmentsViews/VideoThumbnail.swift +++ b/ios/MattermostShare/Views/ContentViews/AttachmentsViews/VideoThumbnail.swift @@ -38,9 +38,8 @@ struct VideoThumbnail: View { Image(uiImage: thumb!) .resizable() .aspectRatio(contentMode: small ? .fill : .fit) - .frame( - maxWidth: small ? 104 : nil) - .frame(height: small ? 104 : 180) + .frame(width: small ? 64 : nil, height: small ? 64 : 180) + .clipped() .cornerRadius(4) .overlay( RoundedRectangle(cornerRadius: 4) @@ -53,11 +52,11 @@ struct VideoThumbnail: View { startPoint: .topTrailing, endPoint: .bottomLeading)) ) } + .background(Color.theme.centerChannelBg) .overlay( ZStack { RoundedRectangle(cornerRadius: 4) .stroke(hasError ? Color.theme.errorTextColor : borderColor, lineWidth: 1) - .shadow(color: borderColor, radius: 3, x: 0, y: 2) FontIcon.text( FontCode.compassIcons(code: .play), fontsize: 24, @@ -66,7 +65,7 @@ struct VideoThumbnail: View { .padding(.trailing, 6) .padding(.bottom, 6) .frame( - height: small ? 104 : 180, + height: small ? 64 : 180, alignment: .bottomTrailing) } ) diff --git a/ios/MattermostShare/Views/ContentViews/ContentView.swift b/ios/MattermostShare/Views/ContentViews/ContentView.swift index c13c68005..841440e16 100644 --- a/ios/MattermostShare/Views/ContentViews/ContentView.swift +++ b/ios/MattermostShare/Views/ContentViews/ContentView.swift @@ -20,7 +20,7 @@ struct ContentView: View { } var body: some View { - VStack { + VStack(spacing: 12) { if (!linkPreviewUrl.isEmpty) { LinkPreview( link: $linkPreviewUrl, @@ -30,12 +30,11 @@ struct ContentView: View { if (!attachments.isEmpty) { AttachmentsView(attachments: $attachments) - .padding(.horizontal, -20) + .padding(.horizontal, -16) } if (!linkPreviewUrl.isEmpty || !attachments.isEmpty) { Divider() - .padding(.top, 10) } VStack (spacing: 0) { @@ -63,7 +62,6 @@ struct ContentView: View { .padding(.all, 0) Divider() - .padding(.bottom, 10) if hasChannels { FloatingTextField(