Skip to content

Commit e4885b9

Browse files
committed
ios: changes to media views
1 parent 09e6d5a commit e4885b9

File tree

8 files changed

+217
-12
lines changed

8 files changed

+217
-12
lines changed

apple/InlineIOS/Features/Media/DocumentView.swift

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ import UIKit
99
class DocumentView: UIView {
1010
// MARK: - Properties
1111

12-
let fullMessage: FullMessage?
13-
let outgoing: Bool
12+
private var fullMessage: FullMessage?
13+
private var outgoing: Bool
1414
private var isBeingRemoved = false
1515

16+
1617
enum DocumentState: Equatable {
1718
case locallyAvailable
1819
case needsDownload
@@ -147,7 +148,23 @@ class DocumentView: UIView {
147148

148149
// MARK: - Setup & helpers
149150

151+
override var intrinsicContentSize: CGSize {
152+
let targetSize = CGSize(
153+
width: UIView.layoutFittingCompressedSize.width,
154+
height: UIView.layoutFittingCompressedSize.height
155+
)
156+
let stackSize = horizantalStackView.systemLayoutSizeFitting(
157+
targetSize,
158+
withHorizontalFittingPriority: .fittingSizeLevel,
159+
verticalFittingPriority: .fittingSizeLevel
160+
)
161+
return CGSize(width: ceil(stackSize.width + 4), height: ceil(stackSize.height + 2))
162+
}
163+
150164
func setupViews() {
165+
setContentHuggingPriority(.required, for: .horizontal)
166+
setContentCompressionResistancePriority(.required, for: .horizontal)
167+
151168
addSubview(horizantalStackView)
152169
horizantalStackView.addArrangedSubview(fileIconButton)
153170
fileIconButton.addSubview(iconView)
@@ -256,6 +273,8 @@ class DocumentView: UIView {
256273
progressLayer.path = UIBezierPath().cgPath
257274
}
258275

276+
private var fileSizeMinWidthConstraint: NSLayoutConstraint?
277+
259278
func setupContent() {
260279
// Colors
261280
fileNameLabel.textColor = textColor
@@ -268,9 +287,42 @@ class DocumentView: UIView {
268287

269288
// Set fixed width for fileSizeLabel to prevent layout shifts
270289
let maxSizeTextWidth = fileSizeLabel.intrinsicContentSize.width * 1.5
271-
fileSizeLabel.widthAnchor.constraint(greaterThanOrEqualToConstant: maxSizeTextWidth).isActive = true
290+
if let fileSizeMinWidthConstraint {
291+
fileSizeMinWidthConstraint.constant = maxSizeTextWidth
292+
} else {
293+
let constraint = fileSizeLabel.widthAnchor.constraint(greaterThanOrEqualToConstant: maxSizeTextWidth)
294+
constraint.isActive = true
295+
fileSizeMinWidthConstraint = constraint
296+
}
272297

273298
updateFileIcon()
299+
invalidateIntrinsicContentSize()
300+
}
301+
302+
func update(with fullMessage: FullMessage, outgoing: Bool) {
303+
self.fullMessage = fullMessage
304+
self.outgoing = outgoing
305+
isBeingRemoved = false
306+
progressSubscription?.cancel()
307+
progressSubscription = nil
308+
309+
setupContent()
310+
311+
if let document {
312+
documentState = determineDocumentState(document)
313+
} else {
314+
documentState = .needsDownload
315+
}
316+
317+
if fullMessage.message.status == .sending,
318+
fullMessage.message.documentId != nil,
319+
let document
320+
{
321+
documentState = .uploading(bytesSent: 0, totalBytes: Int64(document.size ?? 0))
322+
}
323+
324+
updateUIForDocumentState()
325+
invalidateIntrinsicContentSize()
274326
}
275327

276328
@objc func fileIconButtonTapped() {
@@ -491,6 +543,7 @@ class DocumentView: UIView {
491543

492544
updateFileIcon()
493545
updateProgressLayerColor()
546+
invalidateIntrinsicContentSize()
494547
}
495548

496549
private func updateProgressLayerColor() {

apple/InlineIOS/Features/Media/ImageViewerController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -676,7 +676,7 @@ final class ImageViewerController: UIViewController {
676676

677677
@objc func image(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) {
678678
if let error = error {
679-
print("Error saving image: \(error.localizedDescription)")
679+
Log.shared.error("Failed to save image", error: error)
680680

681681
let alert = UIAlertController(
682682
title: "Save Error",

apple/InlineIOS/Features/Media/NewVideoView.swift

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ final class NewVideoView: UIView {
1717
private var isDownloading = false
1818
private var isPresentingViewer = false
1919
private var pendingViewerURL: URL?
20+
private var downloadProgress: Double = 0
2021

2122
private var hasText: Bool {
2223
fullMessage.message.text?.isEmpty == false
@@ -67,6 +68,24 @@ final class NewVideoView: UIView {
6768
return view
6869
}()
6970

71+
private let downloadProgressView: CircularProgressHostingView = {
72+
let view = CircularProgressHostingView()
73+
view.translatesAutoresizingMaskIntoConstraints = false
74+
view.isHidden = true
75+
return view
76+
}()
77+
78+
private let cancelDownloadButton: UIButton = {
79+
let config = UIImage.SymbolConfiguration(pointSize: 14, weight: .bold)
80+
let button = UIButton(type: .system)
81+
button.translatesAutoresizingMaskIntoConstraints = false
82+
button.setImage(UIImage(systemName: "xmark", withConfiguration: config), for: .normal)
83+
button.tintColor = .white
84+
button.isHidden = true
85+
button.accessibilityLabel = "Cancel download"
86+
return button
87+
}()
88+
7089
private let durationBadge: PillLabel = {
7190
let label = PillLabel()
7291
label.font = .systemFont(ofSize: 10, weight: .semibold)
@@ -198,6 +217,8 @@ final class NewVideoView: UIView {
198217
addSubview(overlayBackground)
199218
overlayBackground.addSubview(overlayIconView)
200219
overlayBackground.addSubview(overlaySpinner)
220+
overlayBackground.addSubview(downloadProgressView)
221+
overlayBackground.addSubview(cancelDownloadButton)
201222
addSubview(durationBadge)
202223

203224
NSLayoutConstraint.activate([
@@ -212,6 +233,14 @@ final class NewVideoView: UIView {
212233
overlaySpinner.centerXAnchor.constraint(equalTo: overlayBackground.centerXAnchor),
213234
overlaySpinner.centerYAnchor.constraint(equalTo: overlayBackground.centerYAnchor),
214235

236+
downloadProgressView.topAnchor.constraint(equalTo: overlayBackground.topAnchor, constant: 3),
237+
downloadProgressView.leadingAnchor.constraint(equalTo: overlayBackground.leadingAnchor, constant: 3),
238+
downloadProgressView.trailingAnchor.constraint(equalTo: overlayBackground.trailingAnchor, constant: -3),
239+
downloadProgressView.bottomAnchor.constraint(equalTo: overlayBackground.bottomAnchor, constant: -3),
240+
241+
cancelDownloadButton.centerXAnchor.constraint(equalTo: overlayBackground.centerXAnchor),
242+
cancelDownloadButton.centerYAnchor.constraint(equalTo: overlayBackground.centerYAnchor),
243+
215244
durationBadge.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 8),
216245
durationBadge.topAnchor.constraint(equalTo: topAnchor, constant: 6),
217246
])
@@ -229,7 +258,9 @@ final class NewVideoView: UIView {
229258

230259
private func setupGestures() {
231260
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap))
261+
tapGesture.delegate = self
232262
addGestureRecognizer(tapGesture)
263+
cancelDownloadButton.addTarget(self, action: #selector(handleCancelDownload), for: .touchUpInside)
233264
}
234265

235266
private func setupMask() {
@@ -282,6 +313,12 @@ final class NewVideoView: UIView {
282313
return
283314
}
284315

316+
progressCancellable?.cancel()
317+
progressCancellable = nil
318+
isDownloading = false
319+
downloadProgress = 0
320+
downloadProgressView.setProgress(0)
321+
285322
setupVideoConstraints()
286323
updateImage()
287324
updateDurationLabel()
@@ -303,12 +340,25 @@ final class NewVideoView: UIView {
303340
.map { FileDownloader.shared.isVideoDownloadActive(videoId: $0.id) } ?? false
304341
let downloading = !isVideoDownloaded && (isDownloading || globalDownloadActive)
305342

306-
if isUploading || downloading {
343+
if isUploading {
307344
overlayIconView.isHidden = true
308345
overlaySpinner.startAnimating()
346+
downloadProgressView.isHidden = true
347+
cancelDownloadButton.isHidden = true
348+
} else if downloading {
349+
overlaySpinner.stopAnimating()
350+
overlayIconView.isHidden = true
351+
downloadProgressView.isHidden = false
352+
cancelDownloadButton.isHidden = false
353+
downloadProgressView.setProgress(downloadProgress)
354+
if let videoId = fullMessage.videoInfo?.id {
355+
bindProgressIfNeeded(videoId: videoId)
356+
}
309357
} else {
310358
overlaySpinner.stopAnimating()
311359
overlayIconView.isHidden = false
360+
downloadProgressView.isHidden = true
361+
cancelDownloadButton.isHidden = true
312362
overlayIconView.image = UIImage(
313363
systemName: isVideoDownloaded ? "play.fill" : "arrow.down",
314364
withConfiguration: UIImage.SymbolConfiguration(pointSize: 22, weight: .semibold)
@@ -388,11 +438,14 @@ final class NewVideoView: UIView {
388438
}
389439

390440
isDownloading = true
441+
downloadProgress = 0
442+
downloadProgressView.setProgress(0)
391443
updateOverlay()
392444
FileDownloader.shared.downloadVideo(video: videoInfo, for: fullMessage.message) { [weak self] _ in
393445
DispatchQueue.main.async { [weak self] in
394446
guard let self else { return }
395447
self.isDownloading = false
448+
self.downloadProgress = 0
396449
self.updateOverlay()
397450
}
398451
}
@@ -411,18 +464,36 @@ final class NewVideoView: UIView {
411464
if let local = self.videoLocalUrl(), FileManager.default.fileExists(atPath: local.path) {
412465
self.progressCancellable = nil
413466
self.isDownloading = false
467+
self.downloadProgress = 0
414468
self.updateOverlay()
415469
return
416470
}
417471

472+
if progress.error == nil, !progress.isComplete {
473+
self.downloadProgress = progress.progress
474+
self.downloadProgressView.setProgress(progress.progress)
475+
}
476+
418477
if progress.error != nil || progress.isComplete {
419478
self.progressCancellable = nil
420479
self.isDownloading = false
480+
self.downloadProgress = 0
421481
self.updateOverlay()
422482
}
423483
}
424484
}
425485

486+
@objc private func handleCancelDownload() {
487+
guard let videoId = fullMessage.videoInfo?.id else { return }
488+
FileDownloader.shared.cancelVideoDownload(videoId: videoId)
489+
progressCancellable?.cancel()
490+
progressCancellable = nil
491+
isDownloading = false
492+
downloadProgress = 0
493+
downloadProgressView.setProgress(0)
494+
updateOverlay()
495+
}
496+
426497
private func videoLocalUrl() -> URL? {
427498
if let localPath = fullMessage.videoInfo?.video.localPath {
428499
return FileCache.getUrl(for: .videos, localPath: localPath)
@@ -456,3 +527,12 @@ final class NewVideoView: UIView {
456527
}
457528
}
458529
}
530+
531+
extension NewVideoView: UIGestureRecognizerDelegate {
532+
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
533+
if let touchedView = touch.view, touchedView.isDescendant(of: cancelDownloadButton) {
534+
return false
535+
}
536+
return true
537+
}
538+
}

apple/InlineIOS/Features/Media/PhotoView.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,24 @@ final class PhotoView: UIView, QLPreviewControllerDataSource, QLPreviewControlle
150150
}
151151
}
152152

153+
func update(with fullMessage: FullMessage) {
154+
let previousFileId = self.fullMessage.file?.id
155+
let previousLocalPath = self.fullMessage.file?.localPath
156+
self.fullMessage = fullMessage
157+
158+
if previousFileId == fullMessage.file?.id,
159+
previousLocalPath == fullMessage.file?.localPath
160+
{
161+
return
162+
}
163+
164+
NSLayoutConstraint.deactivate(imageConstraints)
165+
imageConstraints.removeAll()
166+
imageView.removeFromSuperview()
167+
setupImage()
168+
setNeedsLayout()
169+
}
170+
153171
private func triggerMessageReload() {
154172
Task { @MainActor in
155173
await MessagesPublisher.shared

apple/InlineIOS/Features/Reactions/MessageReactionView.swift

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ class MessageReactionView: UIView, UIContextMenuInteractionDelegate, UIGestureRe
6060
private lazy var emojiLabel: UILabel = {
6161
let label = UILabel()
6262
label.font = UIFont.systemFont(ofSize: Constants.emojiSize, weight: .medium)
63+
label.translatesAutoresizingMaskIntoConstraints = false
6364
configureEmojiLabel(label)
6465
return label
6566
}()
@@ -123,10 +124,6 @@ class MessageReactionView: UIView, UIContextMenuInteractionDelegate, UIGestureRe
123124
private func setupView() {
124125
configureContainerAppearance()
125126

126-
// Center the emoji and count labels
127-
stackView.distribution = .equalSpacing
128-
stackView.alignment = .center
129-
130127
// Add subviews
131128
addSubview(containerView)
132129
containerView.addSubview(stackView)

apple/InlineIOS/Localizable.xcstrings

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -755,7 +755,7 @@
755755
"No files found in this chat." : {
756756

757757
},
758-
"No photos found in this chat." : {
758+
"No media found in this chat." : {
759759

760760
},
761761
"No Spaces Yet" : {
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import SwiftUI
2+
import UIKit
3+
4+
final class CircularProgressHostingView: UIView {
5+
private let hostingController = UIHostingController(rootView: CircularProgressRing(progress: 0))
6+
7+
override init(frame: CGRect) {
8+
super.init(frame: frame)
9+
10+
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
11+
hostingController.view.backgroundColor = .clear
12+
hostingController.view.isUserInteractionEnabled = false
13+
14+
addSubview(hostingController.view)
15+
NSLayoutConstraint.activate([
16+
hostingController.view.topAnchor.constraint(equalTo: topAnchor),
17+
hostingController.view.leadingAnchor.constraint(equalTo: leadingAnchor),
18+
hostingController.view.trailingAnchor.constraint(equalTo: trailingAnchor),
19+
hostingController.view.bottomAnchor.constraint(equalTo: bottomAnchor),
20+
])
21+
}
22+
23+
@available(*, unavailable)
24+
required init?(coder: NSCoder) {
25+
fatalError("init(coder:) has not been implemented")
26+
}
27+
28+
func setProgress(_ progress: Double) {
29+
let clamped = min(max(progress, 0), 1)
30+
hostingController.rootView = CircularProgressRing(progress: clamped)
31+
}
32+
}
33+
34+
private struct CircularProgressRing: View {
35+
let progress: Double
36+
37+
var body: some View {
38+
ProgressView(value: progress)
39+
.progressViewStyle(.circular)
40+
.tint(Color.white)
41+
.frame(maxWidth: .infinity, maxHeight: .infinity)
42+
}
43+
}

0 commit comments

Comments
 (0)