diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift index 57123d74ba..fcb330c321 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift @@ -14,10 +14,10 @@ struct CIFileView: View { @EnvironmentObject var theme: AppTheme let file: CIFile? let edited: Bool - var smallView: Bool = false + var smallViewSize: CGFloat? var body: some View { - if smallView { + if smallViewSize != nil { fileIndicator() .onTapGesture(perform: fileAction) } else { @@ -201,21 +201,22 @@ struct CIFileView: View { } private func fileIcon(_ icon: String, color: Color = Color(uiColor: .tertiaryLabel), innerIcon: String? = nil, innerIconSize: CGFloat? = nil) -> some View { - ZStack(alignment: .center) { + let size = smallViewSize ?? 30 + return ZStack(alignment: .center) { Image(systemName: icon) .resizable() .aspectRatio(contentMode: .fit) - .frame(width: smallView ? 36 : 30, height: smallView ? 36 : 30) + .frame(width: size, height: size) .foregroundColor(color) if let innerIcon = innerIcon, - let innerIconSize = innerIconSize, (!smallView || file?.showStatusIconInSmallView == true) { + let innerIconSize = innerIconSize, (smallViewSize == nil || file?.showStatusIconInSmallView == true) { Image(systemName: innerIcon) .resizable() .aspectRatio(contentMode: .fit) .frame(maxHeight: 16) .frame(width: innerIconSize, height: innerIconSize) .foregroundColor(.white) - .padding(.top, smallView ? 15 : 12) + .padding(.top, size / 2.5) } } } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift index ce4d2a8181..45a20f03bd 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift @@ -20,12 +20,12 @@ struct CIVoiceView: View { @State var playbackTime: TimeInterval? = nil @Binding var allowMenu: Bool - var smallView: Bool = false + var smallViewSize: CGFloat? @State private var seek: (TimeInterval) -> Void = { _ in } var body: some View { Group { - if smallView { + if smallViewSize != nil { HStack(spacing: 10) { player() playerTime() @@ -65,7 +65,12 @@ struct CIVoiceView: View { } private func player() -> some View { - VoiceMessagePlayer( + let sizeMultiplier: CGFloat = if let sz = smallViewSize { + voiceMessageSizeBasedOnSquareSize(sz) / 56 + } else { + 1 + } + return VoiceMessagePlayer( chat: chat, chatItem: chatItem, recordingFile: recordingFile, @@ -76,7 +81,7 @@ struct CIVoiceView: View { playbackState: $playbackState, playbackTime: $playbackTime, allowMenu: $allowMenu, - sizeMultiplier: smallView ? voiceMessageSizeBasedOnSquareSize(36) / 56 : 1 + sizeMultiplier: sizeMultiplier ) } diff --git a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift index 35cb5b3861..2f6c74f5ed 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift @@ -9,25 +9,34 @@ import SwiftUI import SimpleXChat -private let rowHeights: [DynamicTypeSize: CGFloat] = [ - .xSmall: 68, - .small: 72, - .medium: 76, - .large: 80, - .xLarge: 88, - .xxLarge: 94, - .xxxLarge: 104, - .accessibility1: 90, - .accessibility2: 100, - .accessibility3: 120, - .accessibility4: 130, - .accessibility5: 140 +let dynamicSizes: [ + DynamicTypeSize: ( + rowHeight: CGFloat, + profileImageSize: CGFloat, + mediaSize: CGFloat, + chatInfoSize: CGFloat, + unreadCorner: CGFloat, + unreadPadding: CGFloat + ) +] = [ + .xSmall: (68, 55, 33, 18, 9, 3), + .small: (72, 57, 34, 18, 9, 3), + .medium: (76, 60, 36, 18, 10, 4), + .large: (80, 63, 38, 20, 10, 4), + .xLarge: (88, 67, 41, 20, 10, 4), + .xxLarge: (94, 71, 44, 22, 11, 4), + .xxxLarge: (104, 75, 48, 24, 12, 5), + .accessibility1: (104, 75, 48, 24, 12, 5), + .accessibility2: (114, 75, 48, 24, 12, 5), + .accessibility3: (124, 75, 48, 24, 12, 5), + .accessibility4: (134, 75, 48, 24, 12, 5), + .accessibility5: (144, 75, 48, 24, 12, 5) ] struct ChatListNavLink: View { @EnvironmentObject var chatModel: ChatModel @EnvironmentObject var theme: AppTheme - @Environment(\.dynamicTypeSize) private var dynamicTypeSize + @Environment(\.dynamicTypeSize) private var userFont: DynamicTypeSize @ObservedObject var chat: Chat @State private var showContactRequestDialog = false @State private var showJoinGroupDialog = false @@ -38,6 +47,8 @@ struct ChatListNavLink: View { @State private var inProgress = false @State private var progressByTimeout = false + var dynamicRowHeight: CGFloat { dynamicSizes[userFont]?.rowHeight ?? 80 } + var body: some View { Group { switch chat.chatInfo { @@ -70,7 +81,7 @@ struct ChatListNavLink: View { Group { if contact.activeConn == nil && contact.profile.contactLink != nil { ChatPreviewView(chat: chat, progressByTimeout: Binding.constant(false)) - .frame(height: rowHeights[dynamicTypeSize]) + .frame(height: dynamicRowHeight) .swipeActions(edge: .trailing, allowsFullSwipe: true) { Button { showDeleteContactActionSheet = true @@ -110,7 +121,7 @@ struct ChatListNavLink: View { } .tint(.red) } - .frame(height: rowHeights[dynamicTypeSize]) + .frame(height: dynamicRowHeight) } } .actionSheet(isPresented: $showDeleteContactActionSheet) { @@ -139,7 +150,7 @@ struct ChatListNavLink: View { switch (groupInfo.membership.memberStatus) { case .memInvited: ChatPreviewView(chat: chat, progressByTimeout: $progressByTimeout) - .frame(height: rowHeights[dynamicTypeSize]) + .frame(height: dynamicRowHeight) .swipeActions(edge: .trailing, allowsFullSwipe: true) { joinGroupButton() if groupInfo.canDelete { @@ -159,7 +170,7 @@ struct ChatListNavLink: View { .disabled(inProgress) case .memAccepted: ChatPreviewView(chat: chat, progressByTimeout: Binding.constant(false)) - .frame(height: rowHeights[dynamicTypeSize]) + .frame(height: dynamicRowHeight) .onTapGesture { AlertManager.shared.showAlert(groupInvitationAcceptedAlert()) } @@ -178,7 +189,7 @@ struct ChatListNavLink: View { label: { ChatPreviewView(chat: chat, progressByTimeout: Binding.constant(false)) }, disabled: !groupInfo.ready ) - .frame(height: rowHeights[dynamicTypeSize]) + .frame(height: dynamicRowHeight) .swipeActions(edge: .leading, allowsFullSwipe: true) { markReadButton() toggleFavoriteButton() @@ -205,7 +216,7 @@ struct ChatListNavLink: View { label: { ChatPreviewView(chat: chat, progressByTimeout: Binding.constant(false)) }, disabled: !noteFolder.ready ) - .frame(height: rowHeights[dynamicTypeSize]) + .frame(height: dynamicRowHeight) .swipeActions(edge: .leading, allowsFullSwipe: true) { markReadButton() } @@ -321,7 +332,7 @@ struct ChatListNavLink: View { } .tint(.red) } - .frame(height: rowHeights[dynamicTypeSize]) + .frame(height: dynamicRowHeight) .onTapGesture { showContactRequestDialog = true } .confirmationDialog("Accept connection request?", isPresented: $showContactRequestDialog, titleVisibility: .visible) { Button("Accept") { Task { await acceptContactRequest(incognito: false, contactRequest: contactRequest) } } @@ -349,7 +360,7 @@ struct ChatListNavLink: View { } .tint(theme.colors.primary) } - .frame(height: rowHeights[dynamicTypeSize]) + .frame(height: dynamicRowHeight) .appSheet(isPresented: $showContactConnectionInfo) { Group { if case let .contactConnection(contactConnection) = chat.chatInfo { @@ -469,7 +480,7 @@ struct ChatListNavLink: View { Text("invalid chat data") .foregroundColor(.red) .padding(4) - .frame(height: rowHeights[dynamicTypeSize]) + .frame(height: dynamicRowHeight) .onTapGesture { showInvalidJSON = true } .appSheet(isPresented: $showInvalidJSON) { invalidJSONView(json) diff --git a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift index ce638e8a0a..58329d36f0 100644 --- a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift @@ -12,6 +12,7 @@ import SimpleXChat struct ChatPreviewView: View { @EnvironmentObject var chatModel: ChatModel @EnvironmentObject var theme: AppTheme + @Environment(\.dynamicTypeSize) private var userFont: DynamicTypeSize @ObservedObject var chat: Chat @Binding var progressByTimeout: Bool @State var deleting: Bool = false @@ -21,11 +22,14 @@ struct ChatPreviewView: View { @AppStorage(DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS) private var showChatPreviews = true + var dynamicMediaSize: CGFloat { dynamicSizes[userFont]?.mediaSize ?? 36 } + var dynamicChatInfoSize: CGFloat { dynamicSizes[userFont]?.chatInfoSize ?? 18 } + var body: some View { let cItem = chat.chatItems.last return HStack(spacing: 8) { ZStack(alignment: .bottomTrailing) { - ChatInfoImage(chat: chat, size: 63) + ChatInfoImage(chat: chat, size: dynamicSizes[userFont]?.profileImageSize ?? 63) chatPreviewImageOverlayIcon() .padding([.bottom, .trailing], 1) } @@ -172,7 +176,7 @@ struct ChatPreviewView: View { private func chatPreviewLayout(_ text: Text?, draft: Bool = false, _ hasFilePreview: Bool = false) -> some View { ZStack(alignment: .topTrailing) { let t = text - .lineLimit(2) + .lineLimit(userFont <= .xxxLarge ? 2 : 1) .multilineTextAlignment(.leading) .frame(maxWidth: .infinity, alignment: .topLeading) .padding(.leading, hasFilePreview ? 0 : 8) @@ -192,22 +196,25 @@ struct ChatPreviewView: View { let s = chat.chatStats if s.unreadCount > 0 || s.unreadChat { unreadCountText(s.unreadCount) - .font(.caption) + .font(userFont <= .xxxLarge ? .caption : .caption2) .foregroundColor(.white) - .padding(.horizontal, 4) - .frame(minWidth: 18, minHeight: 18) + .padding(.horizontal, dynamicSizes[userFont]?.unreadPadding ?? 4) + .frame(minWidth: dynamicChatInfoSize, minHeight: dynamicChatInfoSize) .background(chat.chatInfo.ntfsEnabled || chat.chatInfo.chatType == .local ? theme.colors.primary : theme.colors.secondary) - .cornerRadius(10) + .cornerRadius(dynamicSizes[userFont]?.unreadCorner ?? 10) } else if !chat.chatInfo.ntfsEnabled && chat.chatInfo.chatType != .local { Image(systemName: "speaker.slash.fill") + .resizable() + .scaledToFill() + .frame(width: dynamicChatInfoSize, height: dynamicChatInfoSize) .foregroundColor(theme.colors.secondary) } else if chat.chatInfo.chatSettings?.favorite ?? false { Image(systemName: "star.fill") .resizable() .scaledToFill() - .frame(width: 18, height: 18) + .frame(width: dynamicChatInfoSize, height: dynamicChatInfoSize) .padding(.trailing, 1) - .foregroundColor(.secondary.opacity(0.65)) + .foregroundColor(theme.colors.secondary.opacity(0.65)) } else { Color.clear.frame(width: 0) } @@ -293,12 +300,12 @@ struct ChatPreviewView: View { let mc = ci.content.msgContent switch mc { case let .link(_, preview): - smallContentPreview( + smallContentPreview(size: dynamicMediaSize) { ZStack(alignment: .topTrailing) { Image(uiImage: UIImage(base64Encoded: preview.image) ?? UIImage(systemName: "arrow.up.right")!) .resizable() .aspectRatio(contentMode: .fill) - .frame(width: 36, height: 36) + .frame(width: dynamicMediaSize, height: dynamicMediaSize) ZStack { Image(systemName: "arrow.up.right") .resizable() @@ -313,25 +320,25 @@ struct ChatPreviewView: View { .onTapGesture { UIApplication.shared.open(preview.uri) } - ) + } case let .image(_, image): - smallContentPreview( - CIImageView(chatItem: ci, preview: UIImage(base64Encoded: image), maxWidth: 36, smallView: true, showFullScreenImage: $showFullscreenGallery) + smallContentPreview(size: dynamicMediaSize) { + CIImageView(chatItem: ci, preview: UIImage(base64Encoded: image), maxWidth: dynamicMediaSize, smallView: true, showFullScreenImage: $showFullscreenGallery) .environmentObject(ReverseListScrollModel()) - ) + } case let .video(_,image, duration): - smallContentPreview( - CIVideoView(chatItem: ci, preview: UIImage(base64Encoded: image), duration: duration, maxWidth: 36, videoWidth: nil, smallView: true, showFullscreenPlayer: $showFullscreenGallery) + smallContentPreview(size: dynamicMediaSize) { + CIVideoView(chatItem: ci, preview: UIImage(base64Encoded: image), duration: duration, maxWidth: dynamicMediaSize, videoWidth: nil, smallView: true, showFullscreenPlayer: $showFullscreenGallery) .environmentObject(ReverseListScrollModel()) - ) + } case let .voice(_, duration): - smallContentPreviewVoice( - CIVoiceView(chat: chat, chatItem: ci, recordingFile: ci.file, duration: duration, allowMenu: Binding.constant(true), smallView: true) - ) + smallContentPreviewVoice(size: dynamicMediaSize) { + CIVoiceView(chat: chat, chatItem: ci, recordingFile: ci.file, duration: duration, allowMenu: Binding.constant(true), smallViewSize: dynamicMediaSize) + } case .file: - smallContentPreviewFile( - CIFileView(file: ci.file, edited: ci.meta.itemEdited, smallView: true) - ) + smallContentPreviewFile(size: dynamicMediaSize) { + CIFileView(file: ci.file, edited: ci.meta.itemEdited, smallViewSize: dynamicMediaSize) + } default: EmptyView() } } @@ -406,33 +413,28 @@ struct ChatPreviewView: View { } } -func smallContentPreview(_ view: some View) -> some View { - ZStack { - view - .frame(width: 36, height: 36) - } +func smallContentPreview(size: CGFloat, _ view: @escaping () -> some View) -> some View { + view() + .frame(width: size, height: size) .cornerRadius(8) .overlay(RoundedRectangle(cornerSize: CGSize(width: 8, height: 8)) .strokeBorder(.secondary, lineWidth: 0.3, antialiased: true)) - .padding([.top, .leading], 3) + .padding(.vertical, size / 6) + .padding(.leading, 3) .offset(x: 6) } -func smallContentPreviewVoice(_ view: some View) -> some View { - ZStack { - view - .frame(height: voiceMessageSizeBasedOnSquareSize(36)) - } +func smallContentPreviewVoice(size: CGFloat, _ view: @escaping () -> some View) -> some View { + view() + .frame(height: voiceMessageSizeBasedOnSquareSize(size)) + .padding(.vertical, size / 6) .padding(.leading, 8) - .padding(.top, 6) } -func smallContentPreviewFile(_ view: some View) -> some View { - ZStack { - view - .frame(width: 36, height: 36) - } - .padding(.top, 2) +func smallContentPreviewFile(size: CGFloat, _ view: @escaping () -> some View) -> some View { + view() + .frame(width: size, height: size) + .padding(.vertical, size / 7) .padding(.leading, 5) } diff --git a/apps/ios/Shared/Views/ChatList/ContactRequestView.swift b/apps/ios/Shared/Views/ChatList/ContactRequestView.swift index e36a2f7596..2bb93d4101 100644 --- a/apps/ios/Shared/Views/ChatList/ContactRequestView.swift +++ b/apps/ios/Shared/Views/ChatList/ContactRequestView.swift @@ -12,12 +12,13 @@ import SimpleXChat struct ContactRequestView: View { @EnvironmentObject var chatModel: ChatModel @EnvironmentObject var theme: AppTheme + @Environment(\.dynamicTypeSize) private var userFont: DynamicTypeSize var contactRequest: UserContactRequest @ObservedObject var chat: Chat var body: some View { HStack(spacing: 8) { - ChatInfoImage(chat: chat, size: 63) + ChatInfoImage(chat: chat, size: dynamicSizes[userFont]?.profileImageSize ?? 63) .padding(.leading, 4) VStack(alignment: .leading, spacing: 0) { HStack(alignment: .top) {