mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-16 03:09:56 +00:00
Levels
This commit is contained in:
parent
6ff5012e18
commit
865cebe0e7
20
MODULE.bazel.lock
generated
20
MODULE.bazel.lock
generated
@ -81,12 +81,12 @@
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.14/MODULE.bazel": "5e343a3aac88b8d7af3b1b6d2093b55c347b8eefc2e7d1442f7a02dc8fea48ac",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.17/MODULE.bazel": "2ae1d8f4238ec67d7185d8861cb0a2cdf4bc608697c331b95bf990e69b62e64a",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.17/source.json": "4db99b3f55c90ab28d14552aa0632533e3e8e5e9aea0f5c24ac0014282c2a7c5",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513",
|
||||
"https://bcr.bazel.build/modules/rules_cc/0.1.1/source.json": "d61627377bd7dd1da4652063e368d9366fc9a73920bfa396798ad92172cf645c",
|
||||
"https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6",
|
||||
"https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8",
|
||||
"https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/source.json": "c8b1e2c717646f1702290959a3302a178fb639d987ab61d548105019f11e527e",
|
||||
@ -100,8 +100,8 @@
|
||||
"https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab",
|
||||
"https://bcr.bazel.build/modules/rules_java/7.3.2/MODULE.bazel": "50dece891cfdf1741ea230d001aa9c14398062f2b7c066470accace78e412bc2",
|
||||
"https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe",
|
||||
"https://bcr.bazel.build/modules/rules_java/8.11.0/MODULE.bazel": "c3d280bc5ff1038dcb3bacb95d3f6b83da8dd27bba57820ec89ea4085da767ad",
|
||||
"https://bcr.bazel.build/modules/rules_java/8.11.0/source.json": "302b52a39259a85aa06ca3addb9787864ca3e03b432a5f964ea68244397e7544",
|
||||
"https://bcr.bazel.build/modules/rules_java/8.12.0/MODULE.bazel": "8e6590b961f2defdfc2811c089c75716cb2f06c8a4edeb9a8d85eaa64ee2a761",
|
||||
"https://bcr.bazel.build/modules/rules_java/8.12.0/source.json": "cbd5d55d9d38d4008a7d00bee5b5a5a4b6031fcd4a56515c9accbcd42c7be2ba",
|
||||
"https://bcr.bazel.build/modules/rules_java/8.3.2/MODULE.bazel": "7336d5511ad5af0b8615fdc7477535a2e4e723a357b6713af439fe8cf0195017",
|
||||
"https://bcr.bazel.build/modules/rules_java/8.5.1/MODULE.bazel": "d8a9e38cc5228881f7055a6079f6f7821a073df3744d441978e7a43e20226939",
|
||||
"https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7",
|
||||
@ -151,15 +151,15 @@
|
||||
"https://bcr.bazel.build/modules/xcodeproj/8.27.3/MODULE.bazel": "49276599207dae3df1e4336c2067505323dfb0606b53ef63e144087d1226e0eb",
|
||||
"https://bcr.bazel.build/modules/xcodeproj/8.27.3/source.json": "bbbb718187dcbdfbb3a9a0ec7d49446cdf48c67657cafd79b5cf33aa8918f608",
|
||||
"https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0",
|
||||
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/MODULE.bazel": "af322bc08976524477c79d1e45e241b6efbeb918c497e8840b8ab116802dda79",
|
||||
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/source.json": "2be409ac3c7601245958cd4fcdff4288be79ed23bd690b4b951f500d54ee6e7d",
|
||||
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca",
|
||||
"https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/source.json": "22bc55c47af97246cfc093d0acf683a7869377de362b5d1c552c2c2e16b7a806",
|
||||
"https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198"
|
||||
},
|
||||
"selectedYankedVersions": {},
|
||||
"moduleExtensions": {
|
||||
"@@apple_support+//crosstool:setup.bzl%apple_cc_configure_extension": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "IK7QnlhcNBu2jc4wZoGZeDTu3keF2LldFiFUINRcKvo=",
|
||||
"bzlTransitiveDigest": "RjubjYIojbv0PxTpnoknalV9QzT9asbV7elDuN7m2A4=",
|
||||
"usagesDigest": "lfcV4HxPD+NLaRIT/v7BtSGFgE7c9xrWU7jDiwBAxzo=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
@ -190,7 +190,7 @@
|
||||
},
|
||||
"@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "sFhcgPbDQehmbD1EOXzX4H1q/CD5df8zwG4kp4jbvr8=",
|
||||
"bzlTransitiveDigest": "hUTp2w+RUVdL7ma5esCXZJAFnX7vLbVfLd7FwnQI6bU=",
|
||||
"usagesDigest": "QI2z8ZUR+mqtbwsf2fLqYdJAkPOHdOV+tF2yVAUgRzw=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
@ -290,7 +290,7 @@
|
||||
},
|
||||
"@@rules_xcodeproj+//xcodeproj:extensions.bzl%internal": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "+kmqZtEKFY8zgqpV6mrwdQkTJqGUZhL8b3ZMsxrqSyc=",
|
||||
"bzlTransitiveDigest": "6MYik+6MZUO7rOzaI0dUJYVD8dJrR1Q2rT+5vo1j73U=",
|
||||
"usagesDigest": "fvsnMonVwKDYnBfww4bXuYie3WU0d9VSqT2gePSdQco=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
@ -312,7 +312,7 @@
|
||||
},
|
||||
"@@rules_xcodeproj+//xcodeproj:extensions.bzl%non_module_deps": {
|
||||
"general": {
|
||||
"bzlTransitiveDigest": "+kmqZtEKFY8zgqpV6mrwdQkTJqGUZhL8b3ZMsxrqSyc=",
|
||||
"bzlTransitiveDigest": "6MYik+6MZUO7rOzaI0dUJYVD8dJrR1Q2rT+5vo1j73U=",
|
||||
"usagesDigest": "jzxYhnOC9BE0dJ0biFLfxWXi/+R19uAAZkJ0p9CY0JI=",
|
||||
"recordedFileInputs": {},
|
||||
"recordedDirentsInputs": {},
|
||||
|
||||
@ -5122,8 +5122,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
var price: Int?
|
||||
|
||||
if let globalPostSearchStateValue = self.globalPostSearchStateValue, globalPostSearchStateValue.remainingFreeSearches == 0 {
|
||||
//TODO:localize
|
||||
price = 10
|
||||
price = Int(globalPostSearchStateValue.price.value)
|
||||
}
|
||||
|
||||
self.approvedGlobalPostQueryState.set(ApprovedGlobalPostQueryState(
|
||||
@ -5419,7 +5418,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
emptyResultsButtonContent = .searchQuery(query)
|
||||
} else {
|
||||
emptyResultsTitle = "Limit Reached"
|
||||
emptyResultsText = "You can make up to\n10 search queries per day."
|
||||
emptyResultsText = "You can make up to\n\(globalSearchStateValue.totalFreeSearches) search queries per day."
|
||||
|
||||
emptyResultsButtonContent = .paidSearch(
|
||||
price: Int(globalSearchStateValue.price.value),
|
||||
|
||||
@ -1462,7 +1462,7 @@ public func openPostbox(basePath: String, seedConfiguration: SeedConfiguration,
|
||||
|
||||
#if DEBUG
|
||||
//debugSaveState(basePath: basePath + "/db", name: "previous2")
|
||||
debugRestoreState(basePath: basePath + "/db", name: "previous2")
|
||||
//debugRestoreState(basePath: basePath + "/db", name: "previous2")
|
||||
#endif
|
||||
|
||||
let startTime = CFAbsoluteTimeGetCurrent()
|
||||
|
||||
@ -881,7 +881,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
||||
dict[-1115174036] = { return Api.SavedDialog.parse_savedDialog($0) }
|
||||
dict[-881854424] = { return Api.SavedReactionTag.parse_savedReactionTag($0) }
|
||||
dict[514213599] = { return Api.SavedStarGift.parse_savedStarGift($0) }
|
||||
dict[-1810993028] = { return Api.SearchPostsFlood.parse_searchPostsFlood($0) }
|
||||
dict[1040931690] = { return Api.SearchPostsFlood.parse_searchPostsFlood($0) }
|
||||
dict[-911191137] = { return Api.SearchResultsCalendarPeriod.parse_searchResultsCalendarPeriod($0) }
|
||||
dict[2137295719] = { return Api.SearchResultsPosition.parse_searchResultPosition($0) }
|
||||
dict[871426631] = { return Api.SecureCredentialsEncrypted.parse_secureCredentialsEncrypted($0) }
|
||||
|
||||
@ -296,17 +296,18 @@ public extension Api {
|
||||
}
|
||||
public extension Api {
|
||||
enum SearchPostsFlood: TypeConstructorDescription {
|
||||
case searchPostsFlood(flags: Int32, remains: Int32, waitTill: Int32?, starsAmount: Int64)
|
||||
case searchPostsFlood(flags: Int32, totalDaily: Int32, remains: Int32, waitTill: Int32?, starsAmount: Int64)
|
||||
|
||||
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
|
||||
switch self {
|
||||
case .searchPostsFlood(let flags, let remains, let waitTill, let starsAmount):
|
||||
case .searchPostsFlood(let flags, let totalDaily, let remains, let waitTill, let starsAmount):
|
||||
if boxed {
|
||||
buffer.appendInt32(-1810993028)
|
||||
buffer.appendInt32(1040931690)
|
||||
}
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
serializeInt32(totalDaily, buffer: buffer, boxed: false)
|
||||
serializeInt32(remains, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 0) != 0 {serializeInt32(waitTill!, buffer: buffer, boxed: false)}
|
||||
if Int(flags) & Int(1 << 1) != 0 {serializeInt32(waitTill!, buffer: buffer, boxed: false)}
|
||||
serializeInt64(starsAmount, buffer: buffer, boxed: false)
|
||||
break
|
||||
}
|
||||
@ -314,8 +315,8 @@ public extension Api {
|
||||
|
||||
public func descriptionFields() -> (String, [(String, Any)]) {
|
||||
switch self {
|
||||
case .searchPostsFlood(let flags, let remains, let waitTill, let starsAmount):
|
||||
return ("searchPostsFlood", [("flags", flags as Any), ("remains", remains as Any), ("waitTill", waitTill as Any), ("starsAmount", starsAmount as Any)])
|
||||
case .searchPostsFlood(let flags, let totalDaily, let remains, let waitTill, let starsAmount):
|
||||
return ("searchPostsFlood", [("flags", flags as Any), ("totalDaily", totalDaily as Any), ("remains", remains as Any), ("waitTill", waitTill as Any), ("starsAmount", starsAmount as Any)])
|
||||
}
|
||||
}
|
||||
|
||||
@ -325,15 +326,18 @@ public extension Api {
|
||||
var _2: Int32?
|
||||
_2 = reader.readInt32()
|
||||
var _3: Int32?
|
||||
if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() }
|
||||
var _4: Int64?
|
||||
_4 = reader.readInt64()
|
||||
_3 = reader.readInt32()
|
||||
var _4: Int32?
|
||||
if Int(_1!) & Int(1 << 1) != 0 {_4 = reader.readInt32() }
|
||||
var _5: Int64?
|
||||
_5 = reader.readInt64()
|
||||
let _c1 = _1 != nil
|
||||
let _c2 = _2 != nil
|
||||
let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil
|
||||
let _c4 = _4 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 {
|
||||
return Api.SearchPostsFlood.searchPostsFlood(flags: _1!, remains: _2!, waitTill: _3, starsAmount: _4!)
|
||||
let _c3 = _3 != nil
|
||||
let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil
|
||||
let _c5 = _5 != nil
|
||||
if _c1 && _c2 && _c3 && _c4 && _c5 {
|
||||
return Api.SearchPostsFlood.searchPostsFlood(flags: _1!, totalDaily: _2!, remains: _3!, waitTill: _4, starsAmount: _5!)
|
||||
}
|
||||
else {
|
||||
return nil
|
||||
|
||||
@ -2757,11 +2757,12 @@ public extension Api.functions.bots {
|
||||
}
|
||||
}
|
||||
public extension Api.functions.channels {
|
||||
static func checkSearchPostsFlood() -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.SearchPostsFlood>) {
|
||||
static func checkSearchPostsFlood(flags: Int32, query: String?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.SearchPostsFlood>) {
|
||||
let buffer = Buffer()
|
||||
buffer.appendInt32(-1146490591)
|
||||
|
||||
return (FunctionDescription(name: "channels.checkSearchPostsFlood", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.SearchPostsFlood? in
|
||||
buffer.appendInt32(576090389)
|
||||
serializeInt32(flags, buffer: buffer, boxed: false)
|
||||
if Int(flags) & Int(1 << 0) != 0 {serializeString(query!, buffer: buffer, boxed: false)}
|
||||
return (FunctionDescription(name: "channels.checkSearchPostsFlood", parameters: [("flags", String(describing: flags)), ("query", String(describing: query))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.SearchPostsFlood? in
|
||||
let reader = BufferReader(buffer)
|
||||
var result: Api.SearchPostsFlood?
|
||||
if let signature = reader.readInt32() {
|
||||
|
||||
@ -608,8 +608,9 @@ func _internal_searchMessages(account: Account, location: SearchMessagesLocation
|
||||
if let searchFlood {
|
||||
transaction.updatePreferencesEntry(key: PreferencesKeys.globalPostSearchState(), { _ in
|
||||
switch searchFlood {
|
||||
case let .searchPostsFlood(_, remains, waitTill, starsAmount):
|
||||
case let .searchPostsFlood(_, totalDaily, remains, waitTill, starsAmount):
|
||||
return PreferencesEntry(TelegramGlobalPostSearchState(
|
||||
totalFreeSearches: totalDaily,
|
||||
remainingFreeSearches: remains,
|
||||
price: StarsAmount(value: starsAmount, nanos: 0),
|
||||
unlockTimestamp: waitTill
|
||||
@ -1089,7 +1090,7 @@ func _internal_updatedRemotePeer(accountPeerId: PeerId, postbox: Postbox, networ
|
||||
}
|
||||
|
||||
func _internal_refreshGlobalPostSearchState(account: Account) -> Signal<Never, NoError> {
|
||||
return account.network.request(Api.functions.channels.checkSearchPostsFlood())
|
||||
return account.network.request(Api.functions.channels.checkSearchPostsFlood(flags: 0, query: nil))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.SearchPostsFlood?, NoError> in
|
||||
return .single(nil)
|
||||
@ -1101,8 +1102,9 @@ func _internal_refreshGlobalPostSearchState(account: Account) -> Signal<Never, N
|
||||
}
|
||||
transaction.updatePreferencesEntry(key: PreferencesKeys.globalPostSearchState(), { _ in
|
||||
switch result {
|
||||
case let .searchPostsFlood(_, remains, waitTill, starsAmount):
|
||||
case let .searchPostsFlood(_, totalDaily, remains, waitTill, starsAmount):
|
||||
return PreferencesEntry(TelegramGlobalPostSearchState(
|
||||
totalFreeSearches: totalDaily,
|
||||
remainingFreeSearches: remains,
|
||||
price: StarsAmount(value: starsAmount, nanos: 0),
|
||||
unlockTimestamp: waitTill
|
||||
|
||||
@ -38,17 +38,22 @@ public final class StoryPreloadInfo {
|
||||
}
|
||||
|
||||
public final class TelegramGlobalPostSearchState: Codable, Equatable {
|
||||
public let totalFreeSearches: Int32
|
||||
public let remainingFreeSearches: Int32
|
||||
public let price: StarsAmount
|
||||
public let unlockTimestamp: Int32?
|
||||
|
||||
public init(remainingFreeSearches: Int32, price: StarsAmount, unlockTimestamp: Int32?) {
|
||||
public init(totalFreeSearches: Int32, remainingFreeSearches: Int32, price: StarsAmount, unlockTimestamp: Int32?) {
|
||||
self.totalFreeSearches = totalFreeSearches
|
||||
self.remainingFreeSearches = remainingFreeSearches
|
||||
self.price = price
|
||||
self.unlockTimestamp = unlockTimestamp
|
||||
}
|
||||
|
||||
public static func ==(lhs: TelegramGlobalPostSearchState, rhs: TelegramGlobalPostSearchState) -> Bool {
|
||||
if lhs.totalFreeSearches != rhs.totalFreeSearches {
|
||||
return false
|
||||
}
|
||||
if lhs.remainingFreeSearches != rhs.remainingFreeSearches {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -56,7 +56,7 @@ public final class PeerInfoRatingComponent: Component {
|
||||
private let borderLayer: SimpleLayer
|
||||
private let backgroundLayer: SimpleLayer
|
||||
|
||||
private var tempLevel: Int = 1
|
||||
//private var tempLevel: Int = 1
|
||||
|
||||
private var component: PeerInfoRatingComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
@ -81,7 +81,7 @@ public final class PeerInfoRatingComponent: Component {
|
||||
if case .ended = recognizer.state {
|
||||
self.component?.action()
|
||||
|
||||
if self.tempLevel < 10 {
|
||||
/*if self.tempLevel < 10 {
|
||||
self.tempLevel += 1
|
||||
} else {
|
||||
self.tempLevel += 10
|
||||
@ -89,7 +89,7 @@ public final class PeerInfoRatingComponent: Component {
|
||||
if self.tempLevel >= 110 {
|
||||
self.tempLevel = 1
|
||||
}
|
||||
self.state?.updated(transition: .immediate)
|
||||
self.state?.updated(transition: .immediate)*/
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,9 +102,8 @@ public final class PeerInfoRatingComponent: Component {
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
//TODO:localize
|
||||
//let level = component.level
|
||||
let level = self.tempLevel
|
||||
let level = component.level
|
||||
//let level = self.tempLevel
|
||||
|
||||
let iconSize = CGSize(width: 26.0, height: 26.0)
|
||||
|
||||
|
||||
@ -160,6 +160,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/GifVideoLayer",
|
||||
"//submodules/Components/HierarchyTrackingLayer",
|
||||
"//submodules/TelegramUI/Components/PeerInfo/PeerInfoRatingComponent",
|
||||
"//submodules/TelegramUI/Components/PeerInfo/ProfileLevelInfoScreen",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
||||
@ -40,6 +40,7 @@ import PeerInfoPaneNode
|
||||
import MultilineTextComponent
|
||||
import PeerInfoRatingComponent
|
||||
import UndoUI
|
||||
import ProfileLevelInfoScreen
|
||||
|
||||
final class PeerInfoHeaderNavigationTransition {
|
||||
let sourceNavigationBar: NavigationBar
|
||||
@ -195,6 +196,8 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
|
||||
private var validLayout: (width: CGFloat, statusBarHeight: CGFloat, deviceMetrics: DeviceMetrics)?
|
||||
|
||||
private var currentStarRating: TelegramStarRating?
|
||||
|
||||
init(context: AccountContext, controller: PeerInfoScreenImpl, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, isMediaOnly: Bool, isSettings: Bool, isMyProfile: Bool, forumTopicThreadId: Int64?, chatLocation: ChatLocation) {
|
||||
self.context = context
|
||||
self.controller = controller
|
||||
@ -1942,9 +1945,15 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
let apparentBackgroundHeight = (1.0 - transitionFraction) * backgroundHeight + transitionFraction * transitionSourceHeight
|
||||
|
||||
var subtitleRatingSize: CGSize?
|
||||
//TODO:localize
|
||||
//if let cachedData = cachedData as? CachedUserData, let starRating = cachedData.starRating {
|
||||
if "".isEmpty {
|
||||
|
||||
if let cachedData = cachedData as? CachedUserData, let starRating = cachedData.starRating {
|
||||
self.currentStarRating = starRating
|
||||
} else {
|
||||
self.currentStarRating = nil
|
||||
}
|
||||
|
||||
if let cachedData = cachedData as? CachedUserData, let starRating = cachedData.starRating {
|
||||
//if "".isEmpty {
|
||||
let subtitleRating: ComponentView<Empty>
|
||||
var subtitleRatingTransition = ComponentTransition(transition)
|
||||
if let current = self.subtitleRating {
|
||||
@ -1962,13 +1971,12 @@ final class PeerInfoHeaderNode: ASDisplayNode {
|
||||
backgroundColor: ratingBackgroundColor,
|
||||
borderColor: ratingBorderColor,
|
||||
foregroundColor: ratingForegroundColor,
|
||||
//TODO:localize
|
||||
level: 1,//Int(starRating.level),
|
||||
level: Int(starRating.level),
|
||||
action: { [weak self] in
|
||||
guard let self else {
|
||||
guard let self, let peer, let currentStarRating = self.currentStarRating else {
|
||||
return
|
||||
}
|
||||
let _ = self
|
||||
self.controller?.push(ProfileLevelInfoScreen(context: self.context, peer: EnginePeer(peer), starRating: currentStarRating))
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
|
||||
@ -0,0 +1,33 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "ProfileLevelInfoScreen",
|
||||
module_name = "ProfileLevelInfoScreen",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/Display",
|
||||
"//submodules/TelegramPresentationData",
|
||||
"//submodules/PresentationDataUtils",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/Components/ViewControllerComponent",
|
||||
"//submodules/Components/ComponentDisplayAdapters",
|
||||
"//submodules/AppBundle",
|
||||
"//submodules/Markdown",
|
||||
"//submodules/AccountContext",
|
||||
"//submodules/TelegramCore",
|
||||
"//submodules/Components/MultilineTextComponent",
|
||||
"//submodules/Components/BalancedTextComponent",
|
||||
"//submodules/TelegramUI/Components/ButtonComponent",
|
||||
"//submodules/Components/BundleIconComponent",
|
||||
"//submodules/TelegramUI/Components/PlainButtonComponent",
|
||||
"//submodules/PremiumUI",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
@ -0,0 +1,847 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import SwiftSignalKit
|
||||
import Display
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import ComponentFlow
|
||||
import ComponentDisplayAdapters
|
||||
import AccountContext
|
||||
import ViewControllerComponent
|
||||
import MultilineTextComponent
|
||||
import BalancedTextComponent
|
||||
import ButtonComponent
|
||||
import BundleIconComponent
|
||||
import PresentationDataUtils
|
||||
import PlainButtonComponent
|
||||
import Markdown
|
||||
import PremiumUI
|
||||
|
||||
private final class ProfileLevelInfoScreenComponent: Component {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let context: AccountContext
|
||||
let peer: EnginePeer
|
||||
let starRating: TelegramStarRating
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
peer: EnginePeer,
|
||||
starRating: TelegramStarRating
|
||||
) {
|
||||
self.context = context
|
||||
self.peer = peer
|
||||
self.starRating = starRating
|
||||
}
|
||||
|
||||
static func ==(lhs: ProfileLevelInfoScreenComponent, rhs: ProfileLevelInfoScreenComponent) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
private final class ScrollView: UIScrollView {
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
return super.hitTest(point, with: event)
|
||||
}
|
||||
}
|
||||
|
||||
private struct ItemLayout: Equatable {
|
||||
var containerSize: CGSize
|
||||
var containerInset: CGFloat
|
||||
var bottomInset: CGFloat
|
||||
var topInset: CGFloat
|
||||
|
||||
init(containerSize: CGSize, containerInset: CGFloat, bottomInset: CGFloat, topInset: CGFloat) {
|
||||
self.containerSize = containerSize
|
||||
self.containerInset = containerInset
|
||||
self.bottomInset = bottomInset
|
||||
self.topInset = topInset
|
||||
}
|
||||
}
|
||||
|
||||
final class View: UIView, UIScrollViewDelegate {
|
||||
private let dimView: UIView
|
||||
private let backgroundLayer: SimpleLayer
|
||||
private let navigationBarContainer: SparseContainerView
|
||||
private let navigationBackgroundView: BlurredBackgroundView
|
||||
private let navigationBarSeparator: SimpleLayer
|
||||
private let scrollView: ScrollView
|
||||
private let scrollContentClippingView: SparseContainerView
|
||||
private let scrollContentView: UIView
|
||||
|
||||
private let closeButton = ComponentView<Empty>()
|
||||
|
||||
private let peerAvatar = ComponentView<Empty>()
|
||||
|
||||
private let callIconBackground = ComponentView<Empty>()
|
||||
private let callIcon = ComponentView<Empty>()
|
||||
|
||||
private let title = ComponentView<Empty>()
|
||||
private let levelInfo = ComponentView<Empty>()
|
||||
private let descriptionText = ComponentView<Empty>()
|
||||
|
||||
private var items: [ComponentView<Empty>] = []
|
||||
|
||||
private let bottomPanelContainer: UIView
|
||||
private let actionButton = ComponentView<Empty>()
|
||||
|
||||
private let bottomOverscrollLimit: CGFloat
|
||||
|
||||
private var isFirstTimeApplyingModalFactor: Bool = true
|
||||
private var ignoreScrolling: Bool = false
|
||||
|
||||
private var component: ProfileLevelInfoScreenComponent?
|
||||
private weak var state: EmptyComponentState?
|
||||
private var environment: ViewControllerComponentContainer.Environment?
|
||||
private var isUpdating: Bool = false
|
||||
|
||||
private var itemLayout: ItemLayout?
|
||||
private var topOffsetDistance: CGFloat?
|
||||
|
||||
private var cachedCloseImage: UIImage?
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.bottomOverscrollLimit = 200.0
|
||||
|
||||
self.dimView = UIView()
|
||||
|
||||
self.backgroundLayer = SimpleLayer()
|
||||
self.backgroundLayer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
|
||||
self.backgroundLayer.cornerRadius = 10.0
|
||||
|
||||
self.navigationBarContainer = SparseContainerView()
|
||||
|
||||
self.navigationBackgroundView = BlurredBackgroundView(color: .clear, enableBlur: true)
|
||||
self.navigationBarSeparator = SimpleLayer()
|
||||
|
||||
self.scrollView = ScrollView()
|
||||
|
||||
self.scrollContentClippingView = SparseContainerView()
|
||||
self.scrollContentClippingView.clipsToBounds = true
|
||||
|
||||
self.scrollContentView = UIView()
|
||||
|
||||
self.bottomPanelContainer = UIView()
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.addSubview(self.dimView)
|
||||
self.layer.addSublayer(self.backgroundLayer)
|
||||
|
||||
self.scrollView.delaysContentTouches = true
|
||||
self.scrollView.canCancelContentTouches = true
|
||||
self.scrollView.clipsToBounds = false
|
||||
self.scrollView.contentInsetAdjustmentBehavior = .never
|
||||
if #available(iOS 13.0, *) {
|
||||
self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false
|
||||
}
|
||||
self.scrollView.showsVerticalScrollIndicator = false
|
||||
self.scrollView.showsHorizontalScrollIndicator = false
|
||||
self.scrollView.alwaysBounceHorizontal = false
|
||||
self.scrollView.alwaysBounceVertical = true
|
||||
self.scrollView.scrollsToTop = false
|
||||
self.scrollView.delegate = self
|
||||
self.scrollView.clipsToBounds = true
|
||||
|
||||
self.addSubview(self.scrollContentClippingView)
|
||||
self.scrollContentClippingView.addSubview(self.scrollView)
|
||||
|
||||
self.scrollView.addSubview(self.scrollContentView)
|
||||
|
||||
self.addSubview(self.navigationBarContainer)
|
||||
self.addSubview(self.bottomPanelContainer)
|
||||
|
||||
self.navigationBarContainer.addSubview(self.navigationBackgroundView)
|
||||
self.navigationBarContainer.layer.addSublayer(self.navigationBarSeparator)
|
||||
|
||||
self.dimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimTapGesture(_:))))
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
}
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
if !self.ignoreScrolling {
|
||||
self.updateScrolling(transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if !self.bounds.contains(point) {
|
||||
return nil
|
||||
}
|
||||
if !self.backgroundLayer.frame.contains(point) {
|
||||
return self.dimView
|
||||
}
|
||||
|
||||
if let result = self.navigationBarContainer.hitTest(self.convert(point, to: self.navigationBarContainer), with: event) {
|
||||
return result
|
||||
}
|
||||
|
||||
let result = super.hitTest(point, with: event)
|
||||
return result
|
||||
}
|
||||
|
||||
@objc private func dimTapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
guard let environment = self.environment, let controller = environment.controller() else {
|
||||
return
|
||||
}
|
||||
controller.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
private func updateScrolling(transition: ComponentTransition) {
|
||||
guard let environment = self.environment, let controller = environment.controller(), let itemLayout = self.itemLayout else {
|
||||
return
|
||||
}
|
||||
var topOffset = -self.scrollView.bounds.minY + itemLayout.topInset
|
||||
|
||||
let titleTransformFraction: CGFloat = max(0.0, min(1.0, -topOffset / 20.0))
|
||||
|
||||
let navigationAlpha: CGFloat = titleTransformFraction
|
||||
transition.setAlpha(view: self.navigationBackgroundView, alpha: navigationAlpha)
|
||||
transition.setAlpha(layer: self.navigationBarSeparator, alpha: navigationAlpha)
|
||||
|
||||
topOffset = max(0.0, topOffset)
|
||||
transition.setTransform(layer: self.backgroundLayer, transform: CATransform3DMakeTranslation(0.0, topOffset + itemLayout.containerInset, 0.0))
|
||||
|
||||
transition.setPosition(view: self.navigationBarContainer, position: CGPoint(x: 0.0, y: topOffset + itemLayout.containerInset))
|
||||
|
||||
let topOffsetDistance: CGFloat = min(200.0, floor(itemLayout.containerSize.height * 0.25))
|
||||
self.topOffsetDistance = topOffsetDistance
|
||||
var topOffsetFraction = topOffset / topOffsetDistance
|
||||
topOffsetFraction = max(0.0, min(1.0, topOffsetFraction))
|
||||
|
||||
let transitionFactor: CGFloat = 1.0 - topOffsetFraction
|
||||
var modalOverlayTransition = transition
|
||||
if self.isFirstTimeApplyingModalFactor {
|
||||
self.isFirstTimeApplyingModalFactor = false
|
||||
modalOverlayTransition = .spring(duration: 0.5)
|
||||
}
|
||||
if self.isUpdating {
|
||||
DispatchQueue.main.async { [weak controller] in
|
||||
guard let controller else {
|
||||
return
|
||||
}
|
||||
controller.updateModalStyleOverlayTransitionFactor(transitionFactor, transition: modalOverlayTransition.containedViewLayoutTransition)
|
||||
}
|
||||
} else {
|
||||
controller.updateModalStyleOverlayTransitionFactor(transitionFactor, transition: modalOverlayTransition.containedViewLayoutTransition)
|
||||
}
|
||||
}
|
||||
|
||||
func animateIn() {
|
||||
self.dimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
let animateOffset: CGFloat = self.bounds.height - self.backgroundLayer.frame.minY
|
||||
self.scrollContentClippingView.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
self.backgroundLayer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
self.navigationBarContainer.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
self.bottomPanelContainer.layer.animatePosition(from: CGPoint(x: 0.0, y: animateOffset), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
|
||||
}
|
||||
|
||||
func animateOut(completion: @escaping () -> Void) {
|
||||
let animateOffset: CGFloat = self.bounds.height - self.backgroundLayer.frame.minY
|
||||
|
||||
self.dimView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
||||
self.scrollContentClippingView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true, completion: { _ in
|
||||
completion()
|
||||
})
|
||||
self.backgroundLayer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true)
|
||||
self.navigationBarContainer.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true)
|
||||
self.bottomPanelContainer.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: animateOffset), duration: 0.3, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true)
|
||||
|
||||
if let environment = self.environment, let controller = environment.controller() {
|
||||
controller.updateModalStyleOverlayTransitionFactor(0.0, transition: .animated(duration: 0.3, curve: .easeInOut))
|
||||
}
|
||||
}
|
||||
|
||||
func update(component: ProfileLevelInfoScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: ComponentTransition) -> CGSize {
|
||||
self.isUpdating = true
|
||||
defer {
|
||||
self.isUpdating = false
|
||||
}
|
||||
|
||||
let environment = environment[ViewControllerComponentContainer.Environment.self].value
|
||||
let themeUpdated = self.environment?.theme !== environment.theme
|
||||
|
||||
let resetScrolling = self.scrollView.bounds.width != availableSize.width
|
||||
|
||||
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
|
||||
|
||||
self.component = component
|
||||
self.state = state
|
||||
self.environment = environment
|
||||
|
||||
if themeUpdated {
|
||||
self.dimView.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
|
||||
self.backgroundLayer.backgroundColor = environment.theme.actionSheet.opaqueItemBackgroundColor.cgColor
|
||||
|
||||
self.navigationBackgroundView.updateColor(color: environment.theme.rootController.navigationBar.blurredBackgroundColor, transition: .immediate)
|
||||
self.navigationBarSeparator.backgroundColor = environment.theme.rootController.navigationBar.separatorColor.cgColor
|
||||
}
|
||||
|
||||
transition.setFrame(view: self.dimView, frame: CGRect(origin: CGPoint(), size: availableSize))
|
||||
|
||||
var contentHeight: CGFloat = 0.0
|
||||
|
||||
let closeImage: UIImage
|
||||
if let image = self.cachedCloseImage, !themeUpdated {
|
||||
closeImage = image
|
||||
} else {
|
||||
closeImage = generateCloseButtonImage(backgroundColor: environment.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.05), foregroundColor: environment.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.4))!
|
||||
self.cachedCloseImage = closeImage
|
||||
}
|
||||
|
||||
let closeButtonSize = self.closeButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(Button(
|
||||
content: AnyComponent(Image(image: closeImage, size: closeImage.size)),
|
||||
action: { [weak self] in
|
||||
guard let self, let controller = self.environment?.controller() else {
|
||||
return
|
||||
}
|
||||
controller.dismiss()
|
||||
}
|
||||
).minSize(CGSize(width: 62.0, height: 56.0))),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||
)
|
||||
let closeButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - environment.safeInsets.right - closeButtonSize.width, y: 0.0), size: closeButtonSize)
|
||||
if let closeButtonView = self.closeButton.view {
|
||||
if closeButtonView.superview == nil {
|
||||
self.navigationBarContainer.addSubview(closeButtonView)
|
||||
}
|
||||
transition.setFrame(view: closeButtonView, frame: closeButtonFrame)
|
||||
}
|
||||
|
||||
let containerInset: CGFloat = environment.statusBarHeight + 10.0
|
||||
|
||||
let clippingY: CGFloat
|
||||
|
||||
//TODO:localize
|
||||
let titleString: String = "Rating"
|
||||
let descriptionTextString: String = "The rating reflects **\(component.peer.compactDisplayTitle)'s** activity on Telegram. What affects it:"
|
||||
|
||||
let titleSize = self.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: titleString, font: Font.semibold(17.0), textColor: environment.theme.list.itemPrimaryTextColor))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0)
|
||||
)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) * 0.5), y: floor((56.0 - titleSize.height) * 0.5)), size: titleSize)
|
||||
if let titleView = self.title.view {
|
||||
if titleView.superview == nil {
|
||||
self.navigationBarContainer.addSubview(titleView)
|
||||
}
|
||||
titleView.frame = titleFrame
|
||||
}
|
||||
contentHeight += 56.0
|
||||
|
||||
let navigationBackgroundFrame = CGRect(origin: CGPoint(), size: CGSize(width: availableSize.width, height: 54.0))
|
||||
transition.setFrame(view: self.navigationBackgroundView, frame: navigationBackgroundFrame)
|
||||
self.navigationBackgroundView.update(size: navigationBackgroundFrame.size, cornerRadius: 10.0, maskedCorners: [.layerMinXMinYCorner, .layerMaxXMinYCorner], transition: transition.containedViewLayoutTransition)
|
||||
transition.setFrame(layer: self.navigationBarSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: 54.0), size: CGSize(width: availableSize.width, height: UIScreenPixel)))
|
||||
|
||||
let gradientColors: [UIColor]
|
||||
gradientColors = [
|
||||
environment.theme.list.itemCheckColors.fillColor,
|
||||
environment.theme.list.itemCheckColors.fillColor,
|
||||
environment.theme.list.itemCheckColors.fillColor,
|
||||
environment.theme.list.itemCheckColors.fillColor
|
||||
]
|
||||
|
||||
let levelFraction: CGFloat
|
||||
if let nextLevelStars = component.starRating.nextLevelStars {
|
||||
levelFraction = Double(component.starRating.currentLevelStars) / Double(nextLevelStars)
|
||||
} else {
|
||||
levelFraction = 1.0
|
||||
}
|
||||
|
||||
let levelInfoSize = self.levelInfo.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(PremiumLimitDisplayComponent(
|
||||
inactiveColor: environment.theme.list.itemBlocksSeparatorColor.withAlphaComponent(0.5),
|
||||
activeColors: gradientColors,
|
||||
inactiveTitle: component.starRating.nextLevelStars == nil ? "" : "Level \(component.starRating.level + 1)",
|
||||
inactiveValue: "",
|
||||
inactiveTitleColor: environment.theme.list.itemPrimaryTextColor,
|
||||
activeTitle: "",
|
||||
activeValue: "Level \(component.starRating.level)",
|
||||
activeTitleColor: .white,
|
||||
badgeIconName: "Premium/Boost",
|
||||
badgeText: "\(component.starRating.currentLevelStars)",
|
||||
badgePosition: levelFraction,
|
||||
badgeGraphPosition: levelFraction,
|
||||
invertProgress: true,
|
||||
isPremiumDisabled: false
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 200.0)
|
||||
)
|
||||
if let levelInfoView = self.levelInfo.view {
|
||||
if levelInfoView.superview == nil {
|
||||
self.scrollContentView.addSubview(levelInfoView)
|
||||
}
|
||||
levelInfoView.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - levelInfoSize.width) * 0.5), y: contentHeight - 16.0), size: levelInfoSize)
|
||||
}
|
||||
|
||||
contentHeight += 129.0
|
||||
|
||||
let descriptionTextSize = self.descriptionText.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(BalancedTextComponent(
|
||||
text: .markdown(
|
||||
text: descriptionTextString,
|
||||
attributes: MarkdownAttributes(
|
||||
body: MarkdownAttributeSet(font: Font.regular(15.0), textColor: environment.theme.list.itemPrimaryTextColor),
|
||||
bold: MarkdownAttributeSet(font: Font.semibold(15.0), textColor: environment.theme.list.itemPrimaryTextColor),
|
||||
link: MarkdownAttributeSet(font: Font.regular(15.0), textColor: environment.theme.list.itemAccentColor),
|
||||
linkAttribute: { url in
|
||||
return ("URL", url)
|
||||
}
|
||||
)
|
||||
),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.2
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0)
|
||||
)
|
||||
let descriptionTextFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - descriptionTextSize.width) * 0.5), y: contentHeight), size: descriptionTextSize)
|
||||
if let descriptionTextView = self.descriptionText.view {
|
||||
if descriptionTextView.superview == nil {
|
||||
self.scrollContentView.addSubview(descriptionTextView)
|
||||
}
|
||||
transition.setPosition(view: descriptionTextView, position: descriptionTextFrame.center)
|
||||
descriptionTextView.bounds = CGRect(origin: CGPoint(), size: descriptionTextFrame.size)
|
||||
}
|
||||
contentHeight += descriptionTextSize.height
|
||||
|
||||
contentHeight += 24.0
|
||||
|
||||
struct Item {
|
||||
let title: String
|
||||
let text: String
|
||||
let badgeText: String
|
||||
let isBadgeAccent: Bool
|
||||
let icon: String
|
||||
}
|
||||
let items: [Item] = [
|
||||
Item(
|
||||
title: "Gifts from Telegram",
|
||||
text: "100% of the Stars spent on gifts purchased from Telegram.",
|
||||
badgeText: "ADDED",
|
||||
isBadgeAccent: true,
|
||||
icon: "Premium/BoostPerk/CoverColor"
|
||||
),
|
||||
Item(
|
||||
title: "Gifts and Posts from Users",
|
||||
text: "20% of the Stars spent on gifts or posts from users and channels.",
|
||||
badgeText: "ADDED",
|
||||
isBadgeAccent: true,
|
||||
icon: "Premium/BoostPerk/CoverColor"
|
||||
),
|
||||
Item(
|
||||
title: "Refunds and Conversions",
|
||||
text: "10x of refunded Stars and 85% of bought gifts converted to Stars.",
|
||||
badgeText: "DEDUCTED",
|
||||
isBadgeAccent: false,
|
||||
icon: "Premium/BoostPerk/CoverColor"
|
||||
)
|
||||
]
|
||||
|
||||
let itemSpacing: CGFloat = 24.0
|
||||
|
||||
for i in 0 ..< items.count {
|
||||
if i != 0 {
|
||||
contentHeight += itemSpacing
|
||||
}
|
||||
|
||||
let item = items[i]
|
||||
let itemView: ComponentView<Empty>
|
||||
if self.items.count > i {
|
||||
itemView = self.items[i]
|
||||
} else {
|
||||
itemView = ComponentView()
|
||||
self.items.append(itemView)
|
||||
}
|
||||
|
||||
let itemSize = itemView.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(ItemComponent(
|
||||
theme: environment.theme,
|
||||
title: item.title,
|
||||
text: item.text,
|
||||
badge: item.badgeText,
|
||||
isBadgeAccent: item.isBadgeAccent,
|
||||
icon: item.icon
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0)
|
||||
)
|
||||
let itemFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: itemSize)
|
||||
if let itemComponentView = itemView.view {
|
||||
if itemComponentView.superview == nil {
|
||||
self.scrollContentView.addSubview(itemComponentView)
|
||||
}
|
||||
itemComponentView.frame = itemFrame
|
||||
}
|
||||
|
||||
contentHeight += itemSize.height
|
||||
}
|
||||
|
||||
contentHeight += 31.0
|
||||
|
||||
//TODO:localize
|
||||
let actionButtonTitle: String = "Understood"
|
||||
let actionButtonSize = self.actionButton.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(ButtonComponent(
|
||||
background: ButtonComponent.Background(
|
||||
color: environment.theme.list.itemCheckColors.fillColor,
|
||||
foreground: environment.theme.list.itemCheckColors.foregroundColor,
|
||||
pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.9)
|
||||
),
|
||||
content: AnyComponentWithIdentity(
|
||||
id: AnyHashable(0),
|
||||
component: AnyComponent(ButtonTextContentComponent(
|
||||
text: actionButtonTitle,
|
||||
badge: 0,
|
||||
textColor: environment.theme.list.itemCheckColors.foregroundColor,
|
||||
badgeBackground: environment.theme.list.itemCheckColors.foregroundColor,
|
||||
badgeForeground: environment.theme.list.itemCheckColors.fillColor
|
||||
))
|
||||
),
|
||||
isEnabled: true,
|
||||
displaysProgress: false,
|
||||
action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.environment?.controller()?.dismiss()
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 50.0)
|
||||
)
|
||||
|
||||
let bottomPanelHeight = 10.0 + environment.safeInsets.bottom + actionButtonSize.height
|
||||
|
||||
let bottomPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: availableSize.height - bottomPanelHeight), size: CGSize(width: availableSize.width, height: bottomPanelHeight))
|
||||
transition.setFrame(view: self.bottomPanelContainer, frame: bottomPanelFrame)
|
||||
|
||||
let actionButtonFrame = CGRect(origin: CGPoint(x: sideInset, y: 0.0), size: actionButtonSize)
|
||||
if let actionButtonView = self.actionButton.view {
|
||||
if actionButtonView.superview == nil {
|
||||
self.bottomPanelContainer.addSubview(actionButtonView)
|
||||
}
|
||||
transition.setFrame(view: actionButtonView, frame: actionButtonFrame)
|
||||
}
|
||||
|
||||
contentHeight += bottomPanelHeight
|
||||
|
||||
clippingY = bottomPanelFrame.minY - 8.0
|
||||
|
||||
let topInset: CGFloat = max(0.0, availableSize.height - containerInset - contentHeight)
|
||||
|
||||
let scrollContentHeight = max(topInset + contentHeight + containerInset, availableSize.height - containerInset)
|
||||
|
||||
self.itemLayout = ItemLayout(containerSize: availableSize, containerInset: containerInset, bottomInset: environment.safeInsets.bottom, topInset: topInset)
|
||||
|
||||
transition.setFrame(view: self.scrollContentView, frame: CGRect(origin: CGPoint(x: 0.0, y: topInset + containerInset), size: CGSize(width: availableSize.width, height: contentHeight)))
|
||||
|
||||
transition.setPosition(layer: self.backgroundLayer, position: CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0))
|
||||
transition.setBounds(layer: self.backgroundLayer, bounds: CGRect(origin: CGPoint(), size: availableSize))
|
||||
|
||||
let scrollClippingFrame = CGRect(origin: CGPoint(x: sideInset, y: containerInset), size: CGSize(width: availableSize.width - sideInset * 2.0, height: clippingY - containerInset))
|
||||
transition.setPosition(view: self.scrollContentClippingView, position: scrollClippingFrame.center)
|
||||
transition.setBounds(view: self.scrollContentClippingView, bounds: CGRect(origin: CGPoint(x: scrollClippingFrame.minX, y: scrollClippingFrame.minY), size: scrollClippingFrame.size))
|
||||
|
||||
self.ignoreScrolling = true
|
||||
let previousBounds = self.scrollView.bounds
|
||||
transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: availableSize.width, height: availableSize.height)))
|
||||
let contentSize = CGSize(width: availableSize.width, height: scrollContentHeight)
|
||||
if contentSize != self.scrollView.contentSize {
|
||||
self.scrollView.contentSize = contentSize
|
||||
}
|
||||
if resetScrolling {
|
||||
self.scrollView.bounds = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: availableSize)
|
||||
} else {
|
||||
if !previousBounds.isEmpty, !transition.animation.isImmediate {
|
||||
let bounds = self.scrollView.bounds
|
||||
if bounds.maxY != previousBounds.maxY {
|
||||
let offsetY = previousBounds.maxY - bounds.maxY
|
||||
transition.animateBoundsOrigin(view: self.scrollView, from: CGPoint(x: 0.0, y: offsetY), to: CGPoint(), additive: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
self.ignoreScrolling = false
|
||||
self.updateScrolling(transition: transition)
|
||||
|
||||
return availableSize
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<ViewControllerComponentContainer.Environment>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
public class ProfileLevelInfoScreen: ViewControllerComponentContainer {
|
||||
private let context: AccountContext
|
||||
private var isDismissed: Bool = false
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
peer: EnginePeer,
|
||||
starRating: TelegramStarRating
|
||||
) {
|
||||
self.context = context
|
||||
|
||||
super.init(context: context, component: ProfileLevelInfoScreenComponent(
|
||||
context: context,
|
||||
peer: peer,
|
||||
starRating: starRating
|
||||
), navigationBarAppearance: .none)
|
||||
|
||||
self.statusBar.statusBarStyle = .Ignore
|
||||
self.navigationPresentation = .flatModal
|
||||
self.blocksBackgroundWhenInOverlay = true
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
}
|
||||
|
||||
override public func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
self.view.disablesInteractiveModalDismiss = true
|
||||
|
||||
if let componentView = self.node.hostView.componentView as? ProfileLevelInfoScreenComponent.View {
|
||||
componentView.animateIn()
|
||||
}
|
||||
}
|
||||
|
||||
override public func dismiss(completion: (() -> Void)? = nil) {
|
||||
if !self.isDismissed {
|
||||
self.isDismissed = true
|
||||
|
||||
if let componentView = self.node.hostView.componentView as? ProfileLevelInfoScreenComponent.View {
|
||||
componentView.animateOut(completion: { [weak self] in
|
||||
completion?()
|
||||
self?.dismiss(animated: false)
|
||||
})
|
||||
} else {
|
||||
self.dismiss(animated: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func generateCloseButtonImage(backgroundColor: UIColor, foregroundColor: UIColor) -> UIImage? {
|
||||
return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.setFillColor(backgroundColor.cgColor)
|
||||
context.fillEllipse(in: CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
context.setLineWidth(2.0)
|
||||
context.setLineCap(.round)
|
||||
context.setStrokeColor(foregroundColor.cgColor)
|
||||
|
||||
context.beginPath()
|
||||
context.move(to: CGPoint(x: 10.0, y: 10.0))
|
||||
context.addLine(to: CGPoint(x: 20.0, y: 20.0))
|
||||
context.move(to: CGPoint(x: 20.0, y: 10.0))
|
||||
context.addLine(to: CGPoint(x: 10.0, y: 20.0))
|
||||
context.strokePath()
|
||||
})
|
||||
}
|
||||
|
||||
private final class ItemComponent: Component {
|
||||
let theme: PresentationTheme
|
||||
let title: String
|
||||
let text: String
|
||||
let badge: String
|
||||
let isBadgeAccent: Bool
|
||||
let icon: String
|
||||
|
||||
init(
|
||||
theme: PresentationTheme,
|
||||
title: String,
|
||||
text: String,
|
||||
badge: String,
|
||||
isBadgeAccent: Bool,
|
||||
icon: String
|
||||
) {
|
||||
self.theme = theme
|
||||
self.title = title
|
||||
self.text = text
|
||||
self.badge = badge
|
||||
self.isBadgeAccent = isBadgeAccent
|
||||
self.icon = icon
|
||||
}
|
||||
|
||||
static func ==(lhs: ItemComponent, rhs: ItemComponent) -> Bool {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
if lhs.title != rhs.title {
|
||||
return false
|
||||
}
|
||||
if lhs.text != rhs.text {
|
||||
return false
|
||||
}
|
||||
if lhs.badge != rhs.badge {
|
||||
return false
|
||||
}
|
||||
if lhs.isBadgeAccent != rhs.isBadgeAccent {
|
||||
return false
|
||||
}
|
||||
if lhs.icon != rhs.icon {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
let title = ComponentView<Empty>()
|
||||
let text = ComponentView<Empty>()
|
||||
let badgeBackground = ComponentView<Empty>()
|
||||
let badgeText = ComponentView<Empty>()
|
||||
let icon = ComponentView<Empty>()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func update(component: ItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
let leftInset: CGFloat = 44.0
|
||||
let titleSpacing: CGFloat = 5.0
|
||||
let badgeInsets = UIEdgeInsets(top: 2.0, left: 4.0, bottom: 2.0, right: 4.0)
|
||||
let badgeSpacing: CGFloat = 4.0
|
||||
|
||||
let titleSize = self.title.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: component.title, font: Font.semibold(15.0), textColor: component.theme.list.itemPrimaryTextColor)),
|
||||
maximumNumberOfLines: 0
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - leftInset, height: 10000.0)
|
||||
)
|
||||
|
||||
let badgeTextSize = self.badgeText.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: component.badge, font: Font.semibold(11.0), textColor: component.isBadgeAccent ? component.theme.chatList.unreadBadgeActiveTextColor : component.theme.chatList.unreadBadgeInactiveTextColor))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 1000.0, height: 10000.0)
|
||||
)
|
||||
|
||||
let textSize = self.text.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: component.text, font: Font.regular(15.0), textColor: component.theme.list.itemSecondaryTextColor)),
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.2,
|
||||
cutout: TextNodeCutout(topLeft: CGSize(width: badgeInsets.left + badgeTextSize.width + badgeInsets.right + badgeSpacing, height: 6.0))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - leftInset, height: 10000.0)
|
||||
)
|
||||
|
||||
let titleFrame = CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: titleSize)
|
||||
let textFrame = CGRect(origin: CGPoint(x: leftInset, y: titleFrame.maxY + titleSpacing), size: textSize)
|
||||
|
||||
let badgeSize = CGSize(width: badgeInsets.left + badgeTextSize.width + badgeInsets.right, height: badgeInsets.top + badgeTextSize.height + badgeInsets.bottom)
|
||||
let badgeFrame = CGRect(origin: CGPoint(x: leftInset, y: textFrame.minY), size: badgeSize)
|
||||
let badgeTextFrame = CGRect(origin: CGPoint(x: badgeFrame.minX + badgeInsets.left, y: badgeFrame.minY + badgeInsets.top), size: badgeTextSize)
|
||||
|
||||
let _ = self.badgeBackground.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(FilledRoundedRectangleComponent(
|
||||
color: component.isBadgeAccent ? component.theme.chatList.unreadBadgeActiveBackgroundColor : component.theme.chatList.unreadBadgeInactiveBackgroundColor,
|
||||
cornerRadius: .value(6.0),
|
||||
smoothCorners: true
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: badgeSize
|
||||
)
|
||||
|
||||
if let titleView = self.title.view {
|
||||
if titleView.superview == nil {
|
||||
self.addSubview(titleView)
|
||||
}
|
||||
titleView.frame = titleFrame
|
||||
}
|
||||
if let textView = self.text.view {
|
||||
if textView.superview == nil {
|
||||
self.addSubview(textView)
|
||||
}
|
||||
textView.frame = textFrame
|
||||
}
|
||||
if let badgeBackgroundView = self.badgeBackground.view {
|
||||
if badgeBackgroundView.superview == nil {
|
||||
self.addSubview(badgeBackgroundView)
|
||||
}
|
||||
badgeBackgroundView.frame = badgeFrame
|
||||
}
|
||||
if let badgeTextView = self.badgeText.view {
|
||||
if badgeTextView.superview == nil {
|
||||
self.addSubview(badgeTextView)
|
||||
}
|
||||
badgeTextView.frame = badgeTextFrame
|
||||
}
|
||||
|
||||
let iconSize = self.icon.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(BundleIconComponent(
|
||||
name: component.icon,
|
||||
tintColor: component.theme.list.itemAccentColor
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 200.0, height: 200.0)
|
||||
)
|
||||
if let iconView = self.icon.view {
|
||||
if iconView.superview == nil {
|
||||
self.addSubview(iconView)
|
||||
}
|
||||
iconView.frame = CGRect(origin: CGPoint(x: floor((leftInset - iconSize.width) * 0.5), y: 3.0), size: iconSize)
|
||||
}
|
||||
|
||||
return CGSize(width: availableSize.width, height: textFrame.maxY)
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user