@@ -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+ }
0 commit comments