(IOS) Consistent component for upload item for share extension (#9030)
Co-authored-by: Mattermost Build <build@mattermost.com>
This commit is contained in:
parent
a102b6147e
commit
abb8fc84e9
9 changed files with 141 additions and 121 deletions
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Reference in a new issue