diff --git a/Mixin.xcodeproj/project.pbxproj b/Mixin.xcodeproj/project.pbxproj index 9d1e410e7e..3a98c2a9b4 100644 --- a/Mixin.xcodeproj/project.pbxproj +++ b/Mixin.xcodeproj/project.pbxproj @@ -596,6 +596,7 @@ 94095BB725BE8BC100F44832 /* TextPreviewView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 94095BB625BE8BC100F44832 /* TextPreviewView.xib */; }; 94095BBC25BE8DCF00F44832 /* TextPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94095BBB25BE8DCF00F44832 /* TextPreviewView.swift */; }; 940BAE222629741C00FFF753 /* AuthorizationsContentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 940BAE212629741C00FFF753 /* AuthorizationsContentViewController.swift */; }; + 941317FC27201D4200DABEFF /* PhotoSizeCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94FFEE6627201B3100D91692 /* PhotoSizeCalculator.swift */; }; 9416548325CD2D9A007E76D0 /* OggOpusRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9416548225CD2D9A007E76D0 /* OggOpusRecorder.swift */; }; 9416548B25CD7190007E76D0 /* AudioMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9416548A25CD7190007E76D0 /* AudioMetadata.swift */; }; 941655C425CD7DA7007E76D0 /* AudioSessionClientPriority.swift in Sources */ = {isa = PBXBuildFile; fileRef = 941655C325CD7DA7007E76D0 /* AudioSessionClientPriority.swift */; }; @@ -664,6 +665,8 @@ 94FA8FC92644236F006BC715 /* ConversationMessageComposer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94FA8FC82644236F006BC715 /* ConversationMessageComposer.swift */; }; 94FCB5FF264673C400CCC8FD /* TranscriptPreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94FCB5FE264673C400CCC8FD /* TranscriptPreviewViewController.swift */; }; 94FCB83B264683D900CCC8FD /* TranscriptAttachmentUploadJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94FCB83A264683D900CCC8FD /* TranscriptAttachmentUploadJob.swift */; }; + 94FFEE6027201ADC00D91692 /* PhotoDisplayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94FFEE5F27201ADC00D91692 /* PhotoDisplayTests.swift */; }; + 94FFEE6727201B3100D91692 /* PhotoSizeCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94FFEE6627201B3100D91692 /* PhotoSizeCalculator.swift */; }; 9B748DCD1FA71CEF00BC009B /* CameraViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9B748DCC1FA71CEF00BC009B /* CameraViewController.swift */; }; 9B8D579A1F9F0ADA008A7957 /* Chat.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9B8D57991F9F0ADA008A7957 /* Chat.storyboard */; }; 9BB351671FB19ECB00EDDD2C /* ConversationDateHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BB351661FB19ECB00EDDD2C /* ConversationDateHeaderView.swift */; }; @@ -925,6 +928,13 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 94FFEE6127201ADC00D91692 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 277FF6F71F909A1200DBB2EB /* Project object */; + proxyType = 1; + remoteGlobalIDString = 277FF6FE1F909A1200DBB2EB; + remoteInfo = Mixin; + }; DFE861C12422673B009B1833 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 277FF6F71F909A1200DBB2EB /* Project object */; @@ -1642,6 +1652,9 @@ 94FA8FC82644236F006BC715 /* ConversationMessageComposer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationMessageComposer.swift; sourceTree = ""; }; 94FCB5FE264673C400CCC8FD /* TranscriptPreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranscriptPreviewViewController.swift; sourceTree = ""; }; 94FCB83A264683D900CCC8FD /* TranscriptAttachmentUploadJob.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranscriptAttachmentUploadJob.swift; sourceTree = ""; }; + 94FFEE5D27201ADC00D91692 /* MixinTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MixinTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 94FFEE5F27201ADC00D91692 /* PhotoDisplayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoDisplayTests.swift; sourceTree = ""; }; + 94FFEE6627201B3100D91692 /* PhotoSizeCalculator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoSizeCalculator.swift; sourceTree = ""; }; 9B748DCC1FA71CEF00BC009B /* CameraViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraViewController.swift; sourceTree = ""; }; 9B8D57991F9F0ADA008A7957 /* Chat.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Chat.storyboard; sourceTree = ""; }; 9BB351661FB19ECB00EDDD2C /* ConversationDateHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationDateHeaderView.swift; sourceTree = ""; }; @@ -1943,6 +1956,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 94FFEE5A27201ADC00D91692 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; DFE861B62422673B009B1833 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -1979,6 +1999,7 @@ E090B9F823B3957E0088355B /* MixinNotificationService */, DFE861BA2422673B009B1833 /* MixinShare */, 7B5E5D21255055E8003FFDE0 /* MixinAppGroupAccess */, + 94FFEE5E27201ADC00D91692 /* MixinTests */, 277FF7001F909A1200DBB2EB /* Products */, 506A45AFF37DF0AFA7DD4EEE /* Pods */, DBADD0FED15BCB65F6FF70EB /* Frameworks */, @@ -1992,6 +2013,7 @@ E090B9F723B3957E0088355B /* MixinNotificationService.appex */, DFE861B92422673B009B1833 /* MixinShare.appex */, 7B5E5D20255055E8003FFDE0 /* MixinAppGroupAccess.app */, + 94FFEE5D27201ADC00D91692 /* MixinTests.xctest */, ); name = Products; sourceTree = ""; @@ -2134,6 +2156,7 @@ 7BF19C21241E0DB600B192D5 /* UserPickedLocation.swift */, 7B9F06C520A9858D0095A51B /* VideoMessageViewModel.swift */, 7BF9969420CA6F3800FD948F /* Waveform.swift */, + 94FFEE6627201B3100D91692 /* PhotoSizeCalculator.swift */, ); path = Model; sourceTree = ""; @@ -2706,6 +2729,14 @@ path = "Fullscreen Popup"; sourceTree = ""; }; + 94FFEE5E27201ADC00D91692 /* MixinTests */ = { + isa = PBXGroup; + children = ( + 94FFEE5F27201ADC00D91692 /* PhotoDisplayTests.swift */, + ); + path = MixinTests; + sourceTree = ""; + }; 9B8D579B1F9F0AF4008A7957 /* Chat */ = { isa = PBXGroup; children = ( @@ -3686,6 +3717,24 @@ productReference = 7B5E5D20255055E8003FFDE0 /* MixinAppGroupAccess.app */; productType = "com.apple.product-type.application"; }; + 94FFEE5C27201ADC00D91692 /* MixinTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 94FFEE6527201ADC00D91692 /* Build configuration list for PBXNativeTarget "MixinTests" */; + buildPhases = ( + 94FFEE5927201ADC00D91692 /* Sources */, + 94FFEE5A27201ADC00D91692 /* Frameworks */, + 94FFEE5B27201ADC00D91692 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 94FFEE6227201ADC00D91692 /* PBXTargetDependency */, + ); + name = MixinTests; + productName = MixinTests; + productReference = 94FFEE5D27201ADC00D91692 /* MixinTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; DFE861B82422673B009B1833 /* MixinShare */ = { isa = PBXNativeTarget; buildConfigurationList = DFE861C62422673B009B1833 /* Build configuration list for PBXNativeTarget "MixinShare" */; @@ -3730,7 +3779,7 @@ 277FF6F71F909A1200DBB2EB /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 1210; + LastSwiftUpdateCheck = 1300; LastUpgradeCheck = 1020; ORGANIZATIONNAME = Mixin; TargetAttributes = { @@ -3754,6 +3803,10 @@ CreatedOnToolsVersion = 12.1; ProvisioningStyle = Automatic; }; + 94FFEE5C27201ADC00D91692 = { + CreatedOnToolsVersion = 13.0; + TestTargetID = 277FF6FE1F909A1200DBB2EB; + }; DFE861B82422673B009B1833 = { CreatedOnToolsVersion = 11.3.1; ProvisioningStyle = Automatic; @@ -3782,6 +3835,7 @@ E090B9F623B3957E0088355B /* MixinNotificationService */, DFE861B82422673B009B1833 /* MixinShare */, 7B5E5D1F255055E8003FFDE0 /* MixinAppGroupAccess */, + 94FFEE5C27201ADC00D91692 /* MixinTests */, ); }; /* End PBXProject section */ @@ -3944,6 +3998,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 94FFEE5B27201ADC00D91692 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; DFE861B72422673B009B1833 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -4570,6 +4631,7 @@ 7BEEC0121FDEA19300514A48 /* EncryptionHintViewModel.swift in Sources */, DFC0ECFC23BB610C0091E7AC /* PhoneContactAPI.swift in Sources */, E0D4F5F523A9D803008F0189 /* NotificationManager.swift in Sources */, + 94FFEE6727201B3100D91692 /* PhotoSizeCalculator.swift in Sources */, 7BADC842204C20820016B0FC /* ChangeNumberVerificationCodeViewController.swift in Sources */, 7C66F029268A0384006D8462 /* AppPageCell.swift in Sources */, 7B2A116122C20F5F00AD029C /* PlayerView.swift in Sources */, @@ -5036,6 +5098,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 94FFEE5927201ADC00D91692 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 94FFEE6027201ADC00D91692 /* PhotoDisplayTests.swift in Sources */, + 941317FC27201D4200DABEFF /* PhotoSizeCalculator.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; DFE861B52422673B009B1833 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -5066,6 +5137,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + 94FFEE6227201ADC00D91692 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 277FF6FE1F909A1200DBB2EB /* Mixin */; + targetProxy = 94FFEE6127201ADC00D91692 /* PBXContainerItemProxy */; + }; DFE861C22422673B009B1833 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = DFE861B82422673B009B1833 /* MixinShare */; @@ -5559,6 +5635,63 @@ }; name = Release; }; + 94FFEE6327201ADC00D91692 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 5EL4S7LCG6; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = one.mixin.MixinTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Mixin.app/Mixin"; + }; + name = Debug; + }; + 94FFEE6427201ADC00D91692 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 5EL4S7LCG6; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = one.mixin.MixinTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Mixin.app/Mixin"; + }; + name = Release; + }; DFE861C42422673B009B1833 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 60A0F3EECBE8095220D78A70 /* Pods-MixinShare.debug.xcconfig */; @@ -5695,6 +5828,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 94FFEE6527201ADC00D91692 /* Build configuration list for PBXNativeTarget "MixinTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 94FFEE6327201ADC00D91692 /* Debug */, + 94FFEE6427201ADC00D91692 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; DFE861C62422673B009B1833 /* Build configuration list for PBXNativeTarget "MixinShare" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/Mixin/UserInterface/Controllers/Chat/Cells/PhotoRepresentableMessageCell.swift b/Mixin/UserInterface/Controllers/Chat/Cells/PhotoRepresentableMessageCell.swift index 0c5d2ad485..2408e9806a 100644 --- a/Mixin/UserInterface/Controllers/Chat/Cells/PhotoRepresentableMessageCell.swift +++ b/Mixin/UserInterface/Controllers/Chat/Cells/PhotoRepresentableMessageCell.swift @@ -45,7 +45,7 @@ class PhotoRepresentableMessageCell: ImageMessageCell { } selectedOverlapView.frame = contentImageWrapperView.bounds contentImageWrapperView.position = viewModel.layoutPosition - contentImageWrapperView.aspectRatio = viewModel.contentRatio + contentImageWrapperView.aspectRatio = viewModel.contentSize trailingInfoBackgroundView.frame = viewModel.trailingInfoBackgroundFrame if let origin = viewModel.expandIconOrigin { expandImageView.isHidden = false diff --git a/Mixin/UserInterface/Controllers/Chat/Cells/VideoMessageCell.swift b/Mixin/UserInterface/Controllers/Chat/Cells/VideoMessageCell.swift index db7e31d4c4..e60bc1a463 100644 --- a/Mixin/UserInterface/Controllers/Chat/Cells/VideoMessageCell.swift +++ b/Mixin/UserInterface/Controllers/Chat/Cells/VideoMessageCell.swift @@ -17,7 +17,7 @@ class VideoMessageCell: PhotoRepresentableMessageCell, AttachmentExpirationHinti } override func reloadMedia(viewModel: PhotoRepresentableMessageViewModel) { - contentImageWrapperView.aspectRatio = viewModel.contentRatio + contentImageWrapperView.aspectRatio = viewModel.contentSize contentImageView.image = viewModel.thumbnail if let viewModel = viewModel as? VideoMessageViewModel, viewModel.duration != nil || viewModel.fileSize != nil { let mediaHasDownloaded = viewModel.message.mediaStatus == MediaStatus.DONE.rawValue diff --git a/Mixin/UserInterface/Controllers/Chat/Model/ImageMessageViewModel.swift b/Mixin/UserInterface/Controllers/Chat/Model/ImageMessageViewModel.swift index 83d265bfa7..baa0f0a77a 100644 --- a/Mixin/UserInterface/Controllers/Chat/Model/ImageMessageViewModel.swift +++ b/Mixin/UserInterface/Controllers/Chat/Model/ImageMessageViewModel.swift @@ -4,12 +4,6 @@ class ImageMessageViewModel: DetailInfoMessageViewModel, BackgroundedTrailingInf static let quotingMessageMargin = Margin(leading: 4, trailing: 11, top: 2, bottom: 5) - // This is the fixed width of the bubble, which results the photo width if - // there's no quoted message, or the whole bubble width if there is. - class var bubbleWidth: CGFloat { - 220 - } - override class var supportsQuoting: Bool { true } @@ -38,12 +32,11 @@ class ImageMessageViewModel: DetailInfoMessageViewModel, BackgroundedTrailingInf } override func quoteViewLayoutWidth(from width: CGFloat) -> CGFloat { - Self.bubbleWidth - Self.quotedMessageMargin.horizontal + photoFrame.width } override func layout(width: CGFloat, style: MessageViewModel.Style) { super.layout(width: width, style: style) - let bubbleOrigin: CGPoint if style.contains(.received) { if style.contains(.fullname) { @@ -54,8 +47,13 @@ class ImageMessageViewModel: DetailInfoMessageViewModel, BackgroundedTrailingInf y: Self.bubbleMargin.top) } } else { - bubbleOrigin = CGPoint(x: width - Self.bubbleMargin.leading - Self.bubbleWidth, - y: Self.bubbleMargin.top) + let x: CGFloat + if quotedMessageViewModel == nil { + x = width - Self.bubbleMargin.leading - photoFrame.width + } else { + x = width - Self.bubbleMargin.leading - photoFrame.width - Self.quotingMessageMargin.horizontal + } + bubbleOrigin = CGPoint(x: x, y: Self.bubbleMargin.top) } if let quotedMessageViewModel = quotedMessageViewModel { @@ -75,7 +73,8 @@ class ImageMessageViewModel: DetailInfoMessageViewModel, BackgroundedTrailingInf + quotedMessageViewModel.contentSize.height + Self.quotingMessageMargin.vertical + photoFrame.height - let backgroundSize = CGSize(width: Self.bubbleWidth, height: backgroundHeight) + let backgroundWidth = photoFrame.width + Self.quotingMessageMargin.horizontal + let backgroundSize = CGSize(width: backgroundWidth, height: backgroundHeight) backgroundImageFrame = CGRect(origin: bubbleOrigin, size: backgroundSize) } else { photoFrame.origin = bubbleOrigin diff --git a/Mixin/UserInterface/Controllers/Chat/Model/LocationMessageViewModel.swift b/Mixin/UserInterface/Controllers/Chat/Model/LocationMessageViewModel.swift index ac9105c2f6..7a9a1a4fee 100644 --- a/Mixin/UserInterface/Controllers/Chat/Model/LocationMessageViewModel.swift +++ b/Mixin/UserInterface/Controllers/Chat/Model/LocationMessageViewModel.swift @@ -6,10 +6,6 @@ class LocationMessageViewModel: ImageMessageViewModel { typealias Snapshot = (image: UIImage, annotationCenter: CGPoint) - override class var bubbleWidth: CGFloat { - ScreenWidth.current <= .short ? 260 : 296 - } - let hasAddress: Bool override var statusNormalTintColor: UIColor { @@ -25,6 +21,10 @@ class LocationMessageViewModel: ImageMessageViewModel { var labelsLeadingConstant: CGFloat = 20 var maskFrame: CGRect = .zero + private var bubbleWidth: CGFloat { + ScreenWidth.current <= .short ? 260 : 296 + } + override init(message: MessageItem) { hasAddress = message.location?.address != nil super.init(message: message) @@ -33,9 +33,9 @@ class LocationMessageViewModel: ImageMessageViewModel { override func layout(width: CGFloat, style: MessageViewModel.Style) { let photoWidth: CGFloat if quotedMessageViewModel == nil { - photoWidth = Self.bubbleWidth + photoWidth = bubbleWidth } else { - photoWidth = Self.bubbleWidth - Self.quotingMessageMargin.horizontal + photoWidth = bubbleWidth - Self.quotingMessageMargin.horizontal } photoFrame.size = CGSize(width: photoWidth, height: 180) super.layout(width: width, style: style) @@ -55,7 +55,7 @@ class LocationMessageViewModel: ImageMessageViewModel { height: informationHeight) } else { maskFrame = CGRect(origin: photoFrame.origin, - size: CGSize(width: photoFrame.width, height: photoFrame.height + informationHeight)) + size: CGSize(width: photoFrame.width, height: photoFrame.height + informationHeight)) informationFrame = CGRect(x: 0, y: photoFrame.height, width: photoFrame.width, diff --git a/Mixin/UserInterface/Controllers/Chat/Model/PhotoMessageViewModel.swift b/Mixin/UserInterface/Controllers/Chat/Model/PhotoMessageViewModel.swift index 55967965af..fd0ae18c91 100644 --- a/Mixin/UserInterface/Controllers/Chat/Model/PhotoMessageViewModel.swift +++ b/Mixin/UserInterface/Controllers/Chat/Model/PhotoMessageViewModel.swift @@ -44,7 +44,7 @@ class PhotoMessageViewModel: PhotoRepresentableMessageViewModel, AttachmentLoadi override init(message: MessageItem) { super.init(message: message) updateOperationButtonStyle() - layoutPosition = imageWithRatioMaybeAnArticle(contentRatio) ? .relativeOffset(0) : .center + layoutPosition = imageWithRatioMaybeAnArticle(contentSize) ? .relativeOffset(0) : .center } func beginAttachmentLoading(isTriggeredByUser: Bool) { diff --git a/Mixin/UserInterface/Controllers/Chat/Model/PhotoRepresentableMessageViewModel.swift b/Mixin/UserInterface/Controllers/Chat/Model/PhotoRepresentableMessageViewModel.swift index 90837e3344..2bb7dde72c 100644 --- a/Mixin/UserInterface/Controllers/Chat/Model/PhotoRepresentableMessageViewModel.swift +++ b/Mixin/UserInterface/Controllers/Chat/Model/PhotoRepresentableMessageViewModel.swift @@ -3,42 +3,28 @@ import MixinServices class PhotoRepresentableMessageViewModel: ImageMessageViewModel { - let contentRatio: CGSize + let contentSize: CGSize var operationButtonStyle = NetworkOperationButton.Style.finished(showPlayIcon: false) var layoutPosition = VerticalPositioningImageView.Position.center var expandIconOrigin: CGPoint? - private var maxPresentationHeight: CGFloat { - return Queue.main.autoSync { - AppDelegate.current.mainWindow.bounds.height / 2 - } - } - override init(message: MessageItem) { let mediaWidth = abs(CGFloat(message.mediaWidth ?? 0)) let mediaHeight = abs(CGFloat(message.mediaHeight ?? 0)) if mediaWidth < 1 || mediaHeight < 1 { - contentRatio = CGSize(width: 1, height: 1) + contentSize = CGSize(width: 1, height: 1) } else { - contentRatio = CGSize(width: mediaWidth, height: mediaHeight) + contentSize = CGSize(width: mediaWidth, height: mediaHeight) } super.init(message: message) } override func layout(width: CGFloat, style: MessageViewModel.Style) { - let ratio = contentRatio.width / contentRatio.height - if quotedMessageViewModel == nil { - let photoHeight = min(maxPresentationHeight, round(Self.bubbleWidth / ratio)) - photoFrame.size = CGSize(width: Self.bubbleWidth, height: photoHeight) - } else { - let photoWidth = Self.bubbleWidth - Self.quotingMessageMargin.horizontal - let photoHeight = min(maxPresentationHeight, round(photoWidth / ratio)) - photoFrame.size = CGSize(width: photoWidth, height: photoHeight) - } + photoFrame.size = PhotoSizeCalculator.displaySize(for: contentSize) super.layout(width: width, style: style) layoutTrailingInfoBackgroundFrame() - if imageWithRatioMaybeAnArticle(contentRatio) { + if imageWithRatioMaybeAnArticle(contentSize) { let margin: CGFloat if style.contains(.received) { margin = 9 diff --git a/Mixin/UserInterface/Controllers/Chat/Model/PhotoSizeCalculator.swift b/Mixin/UserInterface/Controllers/Chat/Model/PhotoSizeCalculator.swift new file mode 100644 index 0000000000..ae232c3ea4 --- /dev/null +++ b/Mixin/UserInterface/Controllers/Chat/Model/PhotoSizeCalculator.swift @@ -0,0 +1,59 @@ +import CoreGraphics + +enum PhotoSizeCalculator { + + private enum Height { + static let max: CGFloat = 280 + static let min: CGFloat = 120 + } + + private enum Width { + static let max: CGFloat = 210 + static let min: CGFloat = 120 + } + + static func displaySize(for contentSize: CGSize) -> CGSize { + let contentRatio = contentSize.height / contentSize.width + let height: CGFloat + let width: CGFloat + if contentSize.width > Width.max && contentSize.height > Height.max { + if contentRatio > 1 { + if contentRatio > Height.max / Width.max { + height = Height.max + width = max(Width.min, round(height / contentRatio)) + } else { + width = Width.max + height = min(Height.max, round(width * contentRatio)) + } + } else { + width = Width.max + height = max(Height.min, round(width * contentRatio)) + } + } else if contentSize.height > Height.max && contentSize.width < Width.max { + height = Height.max + width = max(Width.min, round(height / contentRatio)) + } else if contentSize.width > Width.max && contentSize.height < Height.max { + width = Width.max + height = max(Height.min, round(width * contentRatio)) + } else if contentSize.width > Width.min && contentSize.height < Height.min { + height = Height.min + width = min(Width.max, round(height / contentRatio)) + } else if contentSize.height > Height.min && contentSize.width < Width.min { + width = Width.min + height = min(Height.max, round(width * contentRatio)) + } else if contentSize.height < Height.min && contentSize.width < Width.min { + if contentRatio > 1 { + width = Width.min + height = min(Height.max, round(width * contentRatio)) + } else { + height = Height.min + width = min(Width.max, round(height / contentRatio)) + } + } else { + width = contentSize.width + height = contentSize.height + } + return CGSize(width: width, height: height) + } + +} diff --git a/Mixin/UserInterface/Controllers/Chat/Views/GalleryTransitionFromMessageCellView.swift b/Mixin/UserInterface/Controllers/Chat/Views/GalleryTransitionFromMessageCellView.swift index 4d60d531ac..eec12fe6c9 100644 --- a/Mixin/UserInterface/Controllers/Chat/Views/GalleryTransitionFromMessageCellView.swift +++ b/Mixin/UserInterface/Controllers/Chat/Views/GalleryTransitionFromMessageCellView.swift @@ -14,7 +14,7 @@ class GalleryTransitionFromMessageCellView: GalleryTransitionView { guard let viewModel = cell.viewModel as? PhotoRepresentableMessageViewModel else { return } - contentRatio = viewModel.contentRatio + contentRatio = viewModel.contentSize frame = cell.contentImageWrapperView.convert(cell.contentImageWrapperView.bounds, to: superview) imageView.image = cell.contentImageView.image imageWrapperView.imageView.contentMode = cell.contentImageView.contentMode diff --git a/Mixin/UserInterface/Controllers/Chat/Views/VerticalPositioningImageView.swift b/Mixin/UserInterface/Controllers/Chat/Views/VerticalPositioningImageView.swift index 3b523707f0..7bc2e882d6 100644 --- a/Mixin/UserInterface/Controllers/Chat/Views/VerticalPositioningImageView.swift +++ b/Mixin/UserInterface/Controllers/Chat/Views/VerticalPositioningImageView.swift @@ -42,7 +42,7 @@ class VerticalPositioningImageView: UIView { } switch position { case .center: - imageView.frame.size = CGSize(width: bounds.width, height: bounds.width / aspectRatio.width * aspectRatio.height) + imageView.frame.size = CGSize(width: bounds.width, height: max(bounds.height, bounds.width / aspectRatio.width * aspectRatio.height)) imageView.center = CGPoint(x: bounds.width / 2, y: bounds.height / 2) case .relativeOffset(let offset): imageView.frame.size = CGSize(width: bounds.width, height: bounds.width * aspectRatio.height / aspectRatio.width) diff --git a/MixinTests/PhotoDisplayTests.swift b/MixinTests/PhotoDisplayTests.swift new file mode 100644 index 0000000000..85b5181225 --- /dev/null +++ b/MixinTests/PhotoDisplayTests.swift @@ -0,0 +1,73 @@ +import XCTest + +class PhotoDisplayTests: XCTestCase { + + func testPhotoSizeCalculation() { + let contentSizes: [CGSize] = [ + // Bad input + CGSize(width: 1, height: 1), + + // General photos + CGSize(width: 3024, height: 4032), + CGSize(width: 4032, height: 3024), + + // Panoramas + CGSize(width: 11344, height: 3916), + CGSize(width: 3916, height: 11344), + + // Article + CGSize(width: 900, height: 30000), + + // Irregular shapes + CGSize(width: 400, height: 1000), + CGSize(width: 180, height: 1000), + CGSize(width: 110, height: 1000), + + CGSize(width: 400, height: 200), + CGSize(width: 180, height: 200), + CGSize(width: 110, height: 200), + + CGSize(width: 400, height: 100), + CGSize(width: 180, height: 100), + CGSize(width: 110, height: 100), + CGSize(width: 90, height: 100), + ] + + let expected = [ + // Bad input + CGSize(width: 120, height: 120), + + // General photos + CGSize(width: 210, height: 280), + CGSize(width: 210, height: 158), + + // Panoramas + CGSize(width: 210, height: 120), + CGSize(width: 120, height: 280), + + // Article + CGSize(width: 120, height: 280), + + // Irregular shapes + CGSize(width: 120, height: 280), + CGSize(width: 120, height: 280), + CGSize(width: 120, height: 280), + + CGSize(width: 210, height: 120), + CGSize(width: 180, height: 200), + CGSize(width: 120, height: 218), + + CGSize(width: 210, height: 120), + CGSize(width: 210, height: 120), + CGSize(width: 132, height: 120), + CGSize(width: 120, height: 133), + ] + + let calculated = contentSizes.map(PhotoSizeCalculator.displaySize(for:)) + XCTAssertEqual(calculated.count, expected.count) + for i in 0..