@@ -42,6 +42,14 @@ private final class ProfileLevelInfoScreenComponent: Component {
42
42
return true
43
43
}
44
44
45
+ private final class TransitionHint {
46
+ let isChangingPreview : Bool
47
+
48
+ init ( isChangingPreview: Bool ) {
49
+ self . isChangingPreview = isChangingPreview
50
+ }
51
+ }
52
+
45
53
private final class ScrollView : UIScrollView {
46
54
override func hitTest( _ point: CGPoint , with event: UIEvent ? ) -> UIView ? {
47
55
return super. hitTest ( point, with: event)
@@ -98,10 +106,12 @@ private final class ProfileLevelInfoScreenComponent: Component {
98
106
private weak var state : EmptyComponentState ?
99
107
private var environment : ViewControllerComponentContainer . Environment ?
100
108
private var isUpdating : Bool = false
109
+ private var isPreviewingPendingRating : Bool = false
101
110
102
111
private var itemLayout : ItemLayout ?
103
112
private var topOffsetDistance : CGFloat ?
104
113
114
+ private var cachedChevronImage : UIImage ?
105
115
private var cachedCloseImage : UIImage ?
106
116
107
117
override init ( frame: CGRect ) {
@@ -219,7 +229,7 @@ private final class ProfileLevelInfoScreenComponent: Component {
219
229
220
230
transition. setPosition ( view: self . navigationBarContainer, position: CGPoint ( x: 0.0 , y: topOffset + itemLayout. containerInset) )
221
231
222
- let topOffsetDistance : CGFloat = min ( 200.0 , floor ( itemLayout . containerSize . height * 0.25 ) )
232
+ let topOffsetDistance : CGFloat = 80.0
223
233
self . topOffsetDistance = topOffsetDistance
224
234
var topOffsetFraction = topOffset / topOffsetDistance
225
235
topOffsetFraction = max ( 0.0 , min ( 1.0 , topOffsetFraction) )
@@ -273,6 +283,8 @@ private final class ProfileLevelInfoScreenComponent: Component {
273
283
self . isUpdating = false
274
284
}
275
285
286
+ let alphaTransition : ComponentTransition = transition. animation. isImmediate ? . immediate : . easeInOut( duration: 0.16 )
287
+
276
288
let environment = environment [ ViewControllerComponentContainer . Environment. self] . value
277
289
let themeUpdated = self . environment? . theme !== environment. theme
278
290
@@ -337,10 +349,21 @@ private final class ProfileLevelInfoScreenComponent: Component {
337
349
if component. peer. id == component. context. account. peerId {
338
350
descriptionTextString = " The rating reflects your activity on Telegram. What affects it: "
339
351
340
- if let pendingStarRating = component. pendingStarRating {
352
+ let timestamp = Int32 ( Date ( ) . timeIntervalSince1970)
353
+ if let pendingStarRating = component. pendingStarRating, pendingStarRating. timestamp > timestamp {
341
354
if pendingStarRating. rating. stars > component. starRating. stars {
342
355
let pendingPoints = pendingStarRating. rating. stars - component. starRating. stars
343
- secondaryDescriptionTextString = " The rating updates in 21 days after purchases. \n \( pendingPoints) points are pending. "
356
+
357
+ if self . isPreviewingPendingRating {
358
+ secondaryDescriptionTextString = " This will be your rating in 21 days, \n after \( pendingPoints) points are added. [Back >]() "
359
+ } else {
360
+ let dayCount = ( pendingStarRating. timestamp - timestamp) / ( 24 * 60 * 60 )
361
+ if dayCount == 0 {
362
+ secondaryDescriptionTextString = " The rating updates today. \n \( pendingPoints) points are pending. "
363
+ } else {
364
+ secondaryDescriptionTextString = " The rating updates in \( dayCount) days after purchases. \n \( pendingPoints) points are pending. "
365
+ }
366
+ }
344
367
}
345
368
}
346
369
} else {
@@ -378,16 +401,36 @@ private final class ProfileLevelInfoScreenComponent: Component {
378
401
]
379
402
380
403
let levelFraction : CGFloat
381
- if let nextLevelStars = component. starRating. nextLevelStars {
382
- levelFraction = Double ( component. starRating. stars) / Double( nextLevelStars)
383
- } else {
384
- levelFraction = 1.0
385
- }
386
404
387
- let badgeText = starCountString ( Int64 ( component . starRating . stars ) , decimalSeparator : " . " )
405
+ let badgeText : String
388
406
var badgeTextSuffix : String ?
389
- if let nextLevelStars = component. starRating. nextLevelStars {
390
- badgeTextSuffix = " / \( starCountString ( Int64 ( nextLevelStars) , decimalSeparator: " . " ) ) "
407
+ let currentLevel : Int32
408
+ let nextLevel : Int32 ?
409
+
410
+ if let pendingStarRating = component. pendingStarRating, pendingStarRating. rating. stars > component. starRating. stars, self . isPreviewingPendingRating {
411
+ badgeText = starCountString ( Int64 ( pendingStarRating. rating. stars) , decimalSeparator: " . " )
412
+ currentLevel = pendingStarRating. rating. level
413
+ nextLevel = pendingStarRating. rating. nextLevelStars == nil ? nil : currentLevel + 1
414
+ if let nextLevelStars = pendingStarRating. rating. nextLevelStars {
415
+ badgeTextSuffix = " / \( starCountString ( Int64 ( nextLevelStars) , decimalSeparator: " . " ) ) "
416
+ }
417
+ if let nextLevelStars = pendingStarRating. rating. nextLevelStars {
418
+ levelFraction = Double ( pendingStarRating. rating. stars) / Double( nextLevelStars)
419
+ } else {
420
+ levelFraction = 1.0
421
+ }
422
+ } else {
423
+ badgeText = starCountString ( Int64 ( component. starRating. stars) , decimalSeparator: " . " )
424
+ currentLevel = component. starRating. level
425
+ nextLevel = component. starRating. nextLevelStars == nil ? nil : currentLevel + 1
426
+ if let nextLevelStars = component. starRating. nextLevelStars {
427
+ badgeTextSuffix = " / \( starCountString ( Int64 ( nextLevelStars) , decimalSeparator: " . " ) ) "
428
+ }
429
+ if let nextLevelStars = component. starRating. nextLevelStars {
430
+ levelFraction = Double ( component. starRating. stars) / Double( nextLevelStars)
431
+ } else {
432
+ levelFraction = 1.0
433
+ }
391
434
}
392
435
393
436
let levelInfoSize = self . levelInfo. update (
@@ -399,7 +442,7 @@ private final class ProfileLevelInfoScreenComponent: Component {
399
442
inactiveValue: " " ,
400
443
inactiveTitleColor: environment. theme. list. itemPrimaryTextColor,
401
444
activeTitle: " " ,
402
- activeValue: component . starRating . nextLevelStars == nil ? " " : " Level \( component . starRating . level + 1 ) " ,
445
+ activeValue: nextLevel . flatMap { " Level \( $0 ) " } ?? " " ,
403
446
activeTitleColor: . white,
404
447
badgeIconName: " Peer Info/ProfileLevelProgressIcon " ,
405
448
badgeText: badgeText,
@@ -421,32 +464,72 @@ private final class ProfileLevelInfoScreenComponent: Component {
421
464
422
465
contentHeight += 129.0
423
466
467
+ let isChangingPreview = transition. userData ( TransitionHint . self) ? . isChangingPreview ?? false
468
+
424
469
if let secondaryDescriptionTextString {
470
+ if isChangingPreview, let secondaryDescriptionTextView = self . secondaryDescriptionText? . view {
471
+ self . secondaryDescriptionText = nil
472
+ transition. setTransform ( view: secondaryDescriptionTextView, transform: CATransform3DMakeScale ( 0.9 , 0.9 , 1.0 ) )
473
+ alphaTransition. setAlpha ( view: secondaryDescriptionTextView, alpha: 0.0 , completion: { [ weak secondaryDescriptionTextView] _ in
474
+ secondaryDescriptionTextView? . removeFromSuperview ( )
475
+ } )
476
+ }
477
+
425
478
contentHeight -= 8.0
426
479
let secondaryDescriptionText : ComponentView < Empty >
480
+ var secondaryDescriptionTextTransition = transition
427
481
if let current = self . secondaryDescriptionText {
428
482
secondaryDescriptionText = current
429
483
} else {
484
+ secondaryDescriptionTextTransition = . immediate
430
485
secondaryDescriptionText = ComponentView ( )
431
486
self . secondaryDescriptionText = secondaryDescriptionText
432
487
}
488
+
489
+ let secondaryDescriptionAttributedString = NSMutableAttributedString ( attributedString: parseMarkdownIntoAttributedString ( secondaryDescriptionTextString, attributes: MarkdownAttributes (
490
+ body: MarkdownAttributeSet ( font: Font . regular ( 13.0 ) , textColor: environment. theme. list. itemSecondaryTextColor) ,
491
+ bold: MarkdownAttributeSet ( font: Font . semibold ( 13.0 ) , textColor: environment. theme. list. itemSecondaryTextColor) ,
492
+ link: MarkdownAttributeSet ( font: Font . regular ( 13.0 ) , textColor: environment. theme. list. itemAccentColor) ,
493
+ linkAttribute: { url in
494
+ return ( " URL " , url)
495
+ }
496
+ ) ) )
497
+
498
+ let chevronImage : UIImage ?
499
+ if let current = self . cachedChevronImage {
500
+ chevronImage = current
501
+ } else {
502
+ chevronImage = generateTintedImage ( image: UIImage ( bundleImageName: " Item List/InlineTextRightArrow " ) , color: . white)
503
+ self . cachedChevronImage = chevronImage
504
+ }
505
+ if let range = secondaryDescriptionAttributedString. string. range ( of: " > " ) , let chevronImage {
506
+ secondaryDescriptionAttributedString. addAttribute ( . attachment, value: chevronImage, range: NSRange ( range, in: secondaryDescriptionAttributedString. string) )
507
+ }
508
+
433
509
let secondaryDescriptionTextSize = secondaryDescriptionText. update (
434
510
transition: . immediate,
435
511
component: AnyComponent ( BalancedTextComponent (
436
- text: . markdown(
437
- text: secondaryDescriptionTextString,
438
- attributes: MarkdownAttributes (
439
- body: MarkdownAttributeSet ( font: Font . regular ( 13.0 ) , textColor: environment. theme. list. itemSecondaryTextColor) ,
440
- bold: MarkdownAttributeSet ( font: Font . semibold ( 13.0 ) , textColor: environment. theme. list. itemSecondaryTextColor) ,
441
- link: MarkdownAttributeSet ( font: Font . regular ( 13.0 ) , textColor: environment. theme. list. itemAccentColor) ,
442
- linkAttribute: { url in
443
- return ( " URL " , url)
444
- }
445
- )
446
- ) ,
512
+ text: . plain( secondaryDescriptionAttributedString) ,
447
513
horizontalAlignment: . center,
448
514
maximumNumberOfLines: 0 ,
449
- lineSpacing: 0.2
515
+ lineSpacing: 0.2 ,
516
+ highlightColor: environment. theme. list. itemAccentColor. withMultipliedAlpha ( 0.1 ) ,
517
+ highlightAction: { attributes in
518
+ if let _ = attributes [ NSAttributedString . Key ( rawValue: " URL " ) ] {
519
+ return NSAttributedString . Key ( rawValue: " URL " )
520
+ } else {
521
+ return nil
522
+ }
523
+ } ,
524
+ tapAction: { [ weak self] attributes, _ in
525
+ guard let self else {
526
+ return
527
+ }
528
+ self . isPreviewingPendingRating = !self . isPreviewingPendingRating
529
+ var transition : ComponentTransition = . spring( duration: 0.4 )
530
+ transition = transition. withUserData ( TransitionHint ( isChangingPreview: true ) )
531
+ self . state? . updated ( transition: transition)
532
+ }
450
533
) ) ,
451
534
environment: { } ,
452
535
containerSize: CGSize ( width: availableSize. width - sideInset * 2.0 , height: 10000.0 )
@@ -455,8 +538,12 @@ private final class ProfileLevelInfoScreenComponent: Component {
455
538
if let secondaryDescriptionTextView = secondaryDescriptionText. view {
456
539
if secondaryDescriptionTextView. superview == nil {
457
540
self . scrollContentView. addSubview ( secondaryDescriptionTextView)
541
+ if isChangingPreview {
542
+ transition. animateScale ( view: secondaryDescriptionTextView, from: 0.9 , to: 1.0 )
543
+ alphaTransition. animateAlpha ( view: secondaryDescriptionTextView, from: 0.0 , to: 1.0 )
544
+ }
458
545
}
459
- transition . setPosition ( view: secondaryDescriptionTextView, position: secondaryDescriptionTextFrame. center)
546
+ secondaryDescriptionTextTransition . setPosition ( view: secondaryDescriptionTextView, position: secondaryDescriptionTextFrame. center)
460
547
secondaryDescriptionTextView. bounds = CGRect ( origin: CGPoint ( ) , size: secondaryDescriptionTextFrame. size)
461
548
}
462
549
contentHeight += secondaryDescriptionTextSize. height
0 commit comments