mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Various improvements
This commit is contained in:
parent
0f0b14833f
commit
b80f2636d6
@ -41,6 +41,7 @@ public final class HStack<ChildEnvironment: Equatable>: CombinedComponent {
|
|||||||
size.width += child.size.width
|
size.width += child.size.width
|
||||||
size.height = max(size.height, child.size.height)
|
size.height = max(size.height, child.size.height)
|
||||||
}
|
}
|
||||||
|
size.width += context.component.spacing * CGFloat(updatedChildren.count - 1)
|
||||||
|
|
||||||
var nextX = 0.0
|
var nextX = 0.0
|
||||||
for child in updatedChildren {
|
for child in updatedChildren {
|
||||||
|
@ -46,9 +46,10 @@ public final class Image: Component {
|
|||||||
|
|
||||||
func update(component: Image, availableSize: CGSize, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
func update(component: Image, availableSize: CGSize, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
self.image = component.image
|
self.image = component.image
|
||||||
self.tintColor = component.tintColor
|
|
||||||
self.contentMode = component.contentMode
|
self.contentMode = component.contentMode
|
||||||
|
|
||||||
|
transition.setTintColor(view: self, color: component.tintColor ?? .white)
|
||||||
|
|
||||||
return component.size ?? availableSize
|
return component.size ?? availableSize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -573,6 +573,7 @@ private final class PendingInAppPurchaseState: Codable {
|
|||||||
case peers
|
case peers
|
||||||
case boostPeer
|
case boostPeer
|
||||||
case additionalPeerIds
|
case additionalPeerIds
|
||||||
|
case countries
|
||||||
case onlyNewSubscribers
|
case onlyNewSubscribers
|
||||||
case randomId
|
case randomId
|
||||||
case untilDate
|
case untilDate
|
||||||
@ -592,7 +593,7 @@ private final class PendingInAppPurchaseState: Codable {
|
|||||||
case restore
|
case restore
|
||||||
case gift(peerId: EnginePeer.Id)
|
case gift(peerId: EnginePeer.Id)
|
||||||
case giftCode(peerIds: [EnginePeer.Id], boostPeer: EnginePeer.Id?)
|
case giftCode(peerIds: [EnginePeer.Id], boostPeer: EnginePeer.Id?)
|
||||||
case giveaway(boostPeer: EnginePeer.Id, additionalPeerIds: [EnginePeer.Id], onlyNewSubscribers: Bool, randomId: Int64, untilDate: Int32)
|
case giveaway(boostPeer: EnginePeer.Id, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, randomId: Int64, untilDate: Int32)
|
||||||
|
|
||||||
public init(from decoder: Decoder) throws {
|
public init(from decoder: Decoder) throws {
|
||||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
@ -618,6 +619,7 @@ private final class PendingInAppPurchaseState: Codable {
|
|||||||
self = .giveaway(
|
self = .giveaway(
|
||||||
boostPeer: EnginePeer.Id(try container.decode(Int64.self, forKey: .boostPeer)),
|
boostPeer: EnginePeer.Id(try container.decode(Int64.self, forKey: .boostPeer)),
|
||||||
additionalPeerIds: try container.decode([Int64].self, forKey: .randomId).map { EnginePeer.Id($0) },
|
additionalPeerIds: try container.decode([Int64].self, forKey: .randomId).map { EnginePeer.Id($0) },
|
||||||
|
countries: try container.decodeIfPresent([String].self, forKey: .countries) ?? [],
|
||||||
onlyNewSubscribers: try container.decode(Bool.self, forKey: .onlyNewSubscribers),
|
onlyNewSubscribers: try container.decode(Bool.self, forKey: .onlyNewSubscribers),
|
||||||
randomId: try container.decode(Int64.self, forKey: .randomId),
|
randomId: try container.decode(Int64.self, forKey: .randomId),
|
||||||
untilDate: try container.decode(Int32.self, forKey: .untilDate)
|
untilDate: try container.decode(Int32.self, forKey: .untilDate)
|
||||||
@ -644,10 +646,11 @@ private final class PendingInAppPurchaseState: Codable {
|
|||||||
try container.encode(PurposeType.giftCode.rawValue, forKey: .type)
|
try container.encode(PurposeType.giftCode.rawValue, forKey: .type)
|
||||||
try container.encode(peerIds.map { $0.toInt64() }, forKey: .peers)
|
try container.encode(peerIds.map { $0.toInt64() }, forKey: .peers)
|
||||||
try container.encodeIfPresent(boostPeer?.toInt64(), forKey: .boostPeer)
|
try container.encodeIfPresent(boostPeer?.toInt64(), forKey: .boostPeer)
|
||||||
case let .giveaway(boostPeer, additionalPeerIds, onlyNewSubscribers, randomId, untilDate):
|
case let .giveaway(boostPeer, additionalPeerIds, countries, onlyNewSubscribers, randomId, untilDate):
|
||||||
try container.encode(PurposeType.giveaway.rawValue, forKey: .type)
|
try container.encode(PurposeType.giveaway.rawValue, forKey: .type)
|
||||||
try container.encode(boostPeer.toInt64(), forKey: .boostPeer)
|
try container.encode(boostPeer.toInt64(), forKey: .boostPeer)
|
||||||
try container.encode(additionalPeerIds.map { $0.toInt64() }, forKey: .additionalPeerIds)
|
try container.encode(additionalPeerIds.map { $0.toInt64() }, forKey: .additionalPeerIds)
|
||||||
|
try container.encode(countries, forKey: .countries)
|
||||||
try container.encode(onlyNewSubscribers, forKey: .onlyNewSubscribers)
|
try container.encode(onlyNewSubscribers, forKey: .onlyNewSubscribers)
|
||||||
try container.encode(randomId, forKey: .randomId)
|
try container.encode(randomId, forKey: .randomId)
|
||||||
try container.encode(untilDate, forKey: .untilDate)
|
try container.encode(untilDate, forKey: .untilDate)
|
||||||
@ -666,8 +669,8 @@ private final class PendingInAppPurchaseState: Codable {
|
|||||||
self = .gift(peerId: peerId)
|
self = .gift(peerId: peerId)
|
||||||
case let .giftCode(peerIds, boostPeer, _, _):
|
case let .giftCode(peerIds, boostPeer, _, _):
|
||||||
self = .giftCode(peerIds: peerIds, boostPeer: boostPeer)
|
self = .giftCode(peerIds: peerIds, boostPeer: boostPeer)
|
||||||
case let .giveaway(boostPeer, additionalPeerIds, onlyNewSubscribers, randomId, untilDate, _, _):
|
case let .giveaway(boostPeer, additionalPeerIds, countries, onlyNewSubscribers, randomId, untilDate, _, _):
|
||||||
self = .giveaway(boostPeer: boostPeer, additionalPeerIds: additionalPeerIds, onlyNewSubscribers: onlyNewSubscribers, randomId: randomId, untilDate: untilDate)
|
self = .giveaway(boostPeer: boostPeer, additionalPeerIds: additionalPeerIds, countries: countries, onlyNewSubscribers: onlyNewSubscribers, randomId: randomId, untilDate: untilDate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -684,8 +687,8 @@ private final class PendingInAppPurchaseState: Codable {
|
|||||||
return .gift(peerId: peerId, currency: currency, amount: amount)
|
return .gift(peerId: peerId, currency: currency, amount: amount)
|
||||||
case let .giftCode(peerIds, boostPeer):
|
case let .giftCode(peerIds, boostPeer):
|
||||||
return .giftCode(peerIds: peerIds, boostPeer: boostPeer, currency: currency, amount: amount)
|
return .giftCode(peerIds: peerIds, boostPeer: boostPeer, currency: currency, amount: amount)
|
||||||
case let .giveaway(boostPeer, additionalPeerIds, onlyNewSubscribers, randomId, untilDate):
|
case let .giveaway(boostPeer, additionalPeerIds, countries, onlyNewSubscribers, randomId, untilDate):
|
||||||
return .giveaway(boostPeer: boostPeer, additionalPeerIds: additionalPeerIds, onlyNewSubscribers: onlyNewSubscribers, randomId: randomId, untilDate: untilDate, currency: currency, amount: amount)
|
return .giveaway(boostPeer: boostPeer, additionalPeerIds: additionalPeerIds, countries: countries, onlyNewSubscribers: onlyNewSubscribers, randomId: randomId, untilDate: untilDate, currency: currency, amount: amount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -598,6 +598,7 @@ private struct CreateGiveawayControllerState: Equatable {
|
|||||||
var channels: [EnginePeer.Id]
|
var channels: [EnginePeer.Id]
|
||||||
var peers: [EnginePeer.Id]
|
var peers: [EnginePeer.Id]
|
||||||
var selectedMonths: Int32?
|
var selectedMonths: Int32?
|
||||||
|
var countries: [String]
|
||||||
var onlyNewEligible: Bool
|
var onlyNewEligible: Bool
|
||||||
var time: Int32
|
var time: Int32
|
||||||
var pickingTimeLimit = false
|
var pickingTimeLimit = false
|
||||||
@ -620,7 +621,7 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
|
|||||||
}
|
}
|
||||||
|
|
||||||
let expiryTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) + 86400 * 5
|
let expiryTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) + 86400 * 5
|
||||||
let initialState: CreateGiveawayControllerState = CreateGiveawayControllerState(mode: .giveaway, subscriptions: initialSubscriptions, channels: [], peers: [], onlyNewEligible: false, time: expiryTime)
|
let initialState: CreateGiveawayControllerState = CreateGiveawayControllerState(mode: .giveaway, subscriptions: initialSubscriptions, channels: [], peers: [], countries: [], onlyNewEligible: false, time: expiryTime)
|
||||||
|
|
||||||
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
let statePromise = ValuePromise(initialState, ignoreRepeated: true)
|
||||||
let stateValue = Atomic(value: initialState)
|
let stateValue = Atomic(value: initialState)
|
||||||
@ -790,11 +791,14 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
|
|||||||
let (currency, amount) = selectedProduct.storeProduct.priceCurrencyAndAmount
|
let (currency, amount) = selectedProduct.storeProduct.priceCurrencyAndAmount
|
||||||
|
|
||||||
let purpose: AppStoreTransactionPurpose
|
let purpose: AppStoreTransactionPurpose
|
||||||
|
let quantity: Int32
|
||||||
switch state.mode {
|
switch state.mode {
|
||||||
case .giveaway:
|
case .giveaway:
|
||||||
purpose = .giveaway(boostPeer: peerId, additionalPeerIds: state.channels.filter { $0 != peerId }, onlyNewSubscribers: state.onlyNewEligible, randomId: Int64.random(in: .min ..< .max), untilDate: state.time, currency: currency, amount: amount)
|
purpose = .giveaway(boostPeer: peerId, additionalPeerIds: state.channels.filter { $0 != peerId }, countries: state.countries, onlyNewSubscribers: state.onlyNewEligible, randomId: Int64.random(in: .min ..< .max), untilDate: state.time, currency: currency, amount: amount)
|
||||||
|
quantity = selectedProduct.giftOption.storeQuantity
|
||||||
case .gift:
|
case .gift:
|
||||||
purpose = .giftCode(peerIds: state.peers, boostPeer: peerId, currency: currency, amount: amount)
|
purpose = .giftCode(peerIds: state.peers, boostPeer: peerId, currency: currency, amount: amount)
|
||||||
|
quantity = Int32(state.peers.count)
|
||||||
}
|
}
|
||||||
|
|
||||||
updateState { state in
|
updateState { state in
|
||||||
@ -808,7 +812,7 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
|
|||||||
let _ = (context.engine.payments.canPurchasePremium(purpose: purpose)
|
let _ = (context.engine.payments.canPurchasePremium(purpose: purpose)
|
||||||
|> deliverOnMainQueue).startStandalone(next: { [weak controller] available in
|
|> deliverOnMainQueue).startStandalone(next: { [weak controller] available in
|
||||||
if available, let inAppPurchaseManager = context.inAppPurchaseManager {
|
if available, let inAppPurchaseManager = context.inAppPurchaseManager {
|
||||||
let _ = (inAppPurchaseManager.buyProduct(selectedProduct.storeProduct, quantity: selectedProduct.giftOption.storeQuantity, purpose: purpose)
|
let _ = (inAppPurchaseManager.buyProduct(selectedProduct.storeProduct, quantity: quantity, purpose: purpose)
|
||||||
|> deliverOnMainQueue).startStandalone(next: { [weak controller] status in
|
|> deliverOnMainQueue).startStandalone(next: { [weak controller] status in
|
||||||
if case .purchased = status {
|
if case .purchased = status {
|
||||||
if let controller, let navigationController = controller.navigationController as? NavigationController {
|
if let controller, let navigationController = controller.navigationController as? NavigationController {
|
||||||
@ -890,7 +894,7 @@ public func createGiveawayController(context: AccountContext, updatedPresentatio
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
case let .prepaid(prepaidGiveaway):
|
case let .prepaid(prepaidGiveaway):
|
||||||
let _ = (context.engine.payments.launchPrepaidGiveaway(peerId: peerId, id: prepaidGiveaway.id, additionalPeerIds: state.channels.filter { $0 != peerId }, onlyNewSubscribers: state.onlyNewEligible, randomId: Int64.random(in: .min ..< .max), untilDate: state.time)
|
let _ = (context.engine.payments.launchPrepaidGiveaway(peerId: peerId, id: prepaidGiveaway.id, additionalPeerIds: state.channels.filter { $0 != peerId }, countries: state.countries, onlyNewSubscribers: state.onlyNewEligible, randomId: Int64.random(in: .min ..< .max), untilDate: state.time)
|
||||||
|> deliverOnMainQueue).startStandalone(completed: {
|
|> deliverOnMainQueue).startStandalone(completed: {
|
||||||
if let controller, let navigationController = controller.navigationController as? NavigationController {
|
if let controller, let navigationController = controller.navigationController as? NavigationController {
|
||||||
var controllers = navigationController.viewControllers
|
var controllers = navigationController.viewControllers
|
||||||
|
@ -23,6 +23,7 @@ public struct UserLimitsConfiguration: Equatable {
|
|||||||
public let maxStoriesMonthlyCount: Int32
|
public let maxStoriesMonthlyCount: Int32
|
||||||
public let maxStoriesSuggestedReactions: Int32
|
public let maxStoriesSuggestedReactions: Int32
|
||||||
public let maxGiveawayChannelsCount: Int32
|
public let maxGiveawayChannelsCount: Int32
|
||||||
|
public let maxGiveawayCountriesCount: Int32
|
||||||
|
|
||||||
public static var defaultValue: UserLimitsConfiguration {
|
public static var defaultValue: UserLimitsConfiguration {
|
||||||
return UserLimitsConfiguration(
|
return UserLimitsConfiguration(
|
||||||
@ -46,7 +47,8 @@ public struct UserLimitsConfiguration: Equatable {
|
|||||||
maxStoriesWeeklyCount: 7,
|
maxStoriesWeeklyCount: 7,
|
||||||
maxStoriesMonthlyCount: 30,
|
maxStoriesMonthlyCount: 30,
|
||||||
maxStoriesSuggestedReactions: 1,
|
maxStoriesSuggestedReactions: 1,
|
||||||
maxGiveawayChannelsCount: 10
|
maxGiveawayChannelsCount: 10,
|
||||||
|
maxGiveawayCountriesCount: 10
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,7 +73,8 @@ public struct UserLimitsConfiguration: Equatable {
|
|||||||
maxStoriesWeeklyCount: Int32,
|
maxStoriesWeeklyCount: Int32,
|
||||||
maxStoriesMonthlyCount: Int32,
|
maxStoriesMonthlyCount: Int32,
|
||||||
maxStoriesSuggestedReactions: Int32,
|
maxStoriesSuggestedReactions: Int32,
|
||||||
maxGiveawayChannelsCount: Int32
|
maxGiveawayChannelsCount: Int32,
|
||||||
|
maxGiveawayCountriesCount: Int32
|
||||||
) {
|
) {
|
||||||
self.maxPinnedChatCount = maxPinnedChatCount
|
self.maxPinnedChatCount = maxPinnedChatCount
|
||||||
self.maxArchivedPinnedChatCount = maxArchivedPinnedChatCount
|
self.maxArchivedPinnedChatCount = maxArchivedPinnedChatCount
|
||||||
@ -94,6 +97,7 @@ public struct UserLimitsConfiguration: Equatable {
|
|||||||
self.maxStoriesMonthlyCount = maxStoriesMonthlyCount
|
self.maxStoriesMonthlyCount = maxStoriesMonthlyCount
|
||||||
self.maxStoriesSuggestedReactions = maxStoriesSuggestedReactions
|
self.maxStoriesSuggestedReactions = maxStoriesSuggestedReactions
|
||||||
self.maxGiveawayChannelsCount = maxGiveawayChannelsCount
|
self.maxGiveawayChannelsCount = maxGiveawayChannelsCount
|
||||||
|
self.maxGiveawayCountriesCount = maxGiveawayCountriesCount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,5 +143,6 @@ extension UserLimitsConfiguration {
|
|||||||
self.maxStoriesMonthlyCount = getValue("stories_sent_monthly_limit", orElse: defaultValue.maxStoriesMonthlyCount)
|
self.maxStoriesMonthlyCount = getValue("stories_sent_monthly_limit", orElse: defaultValue.maxStoriesMonthlyCount)
|
||||||
self.maxStoriesSuggestedReactions = getValue("stories_suggested_reactions_limit", orElse: defaultValue.maxStoriesMonthlyCount)
|
self.maxStoriesSuggestedReactions = getValue("stories_suggested_reactions_limit", orElse: defaultValue.maxStoriesMonthlyCount)
|
||||||
self.maxGiveawayChannelsCount = getGeneralValue("giveaway_add_peers_max", orElse: defaultValue.maxGiveawayChannelsCount)
|
self.maxGiveawayChannelsCount = getGeneralValue("giveaway_add_peers_max", orElse: defaultValue.maxGiveawayChannelsCount)
|
||||||
|
self.maxGiveawayCountriesCount = getGeneralValue("giveaway_countries_max", orElse: defaultValue.maxGiveawayCountriesCount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,6 +57,7 @@ public enum EngineConfiguration {
|
|||||||
public let maxStoriesMonthlyCount: Int32
|
public let maxStoriesMonthlyCount: Int32
|
||||||
public let maxStoriesSuggestedReactions: Int32
|
public let maxStoriesSuggestedReactions: Int32
|
||||||
public let maxGiveawayChannelsCount: Int32
|
public let maxGiveawayChannelsCount: Int32
|
||||||
|
public let maxGiveawayCountriesCount: Int32
|
||||||
|
|
||||||
public static var defaultValue: UserLimits {
|
public static var defaultValue: UserLimits {
|
||||||
return UserLimits(UserLimitsConfiguration.defaultValue)
|
return UserLimits(UserLimitsConfiguration.defaultValue)
|
||||||
@ -83,7 +84,8 @@ public enum EngineConfiguration {
|
|||||||
maxStoriesWeeklyCount: Int32,
|
maxStoriesWeeklyCount: Int32,
|
||||||
maxStoriesMonthlyCount: Int32,
|
maxStoriesMonthlyCount: Int32,
|
||||||
maxStoriesSuggestedReactions: Int32,
|
maxStoriesSuggestedReactions: Int32,
|
||||||
maxGiveawayChannelsCount: Int32
|
maxGiveawayChannelsCount: Int32,
|
||||||
|
maxGiveawayCountriesCount: Int32
|
||||||
) {
|
) {
|
||||||
self.maxPinnedChatCount = maxPinnedChatCount
|
self.maxPinnedChatCount = maxPinnedChatCount
|
||||||
self.maxArchivedPinnedChatCount = maxArchivedPinnedChatCount
|
self.maxArchivedPinnedChatCount = maxArchivedPinnedChatCount
|
||||||
@ -106,6 +108,7 @@ public enum EngineConfiguration {
|
|||||||
self.maxStoriesMonthlyCount = maxStoriesMonthlyCount
|
self.maxStoriesMonthlyCount = maxStoriesMonthlyCount
|
||||||
self.maxStoriesSuggestedReactions = maxStoriesSuggestedReactions
|
self.maxStoriesSuggestedReactions = maxStoriesSuggestedReactions
|
||||||
self.maxGiveawayChannelsCount = maxGiveawayChannelsCount
|
self.maxGiveawayChannelsCount = maxGiveawayChannelsCount
|
||||||
|
self.maxGiveawayCountriesCount = maxGiveawayCountriesCount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -163,7 +166,8 @@ public extension EngineConfiguration.UserLimits {
|
|||||||
maxStoriesWeeklyCount: userLimitsConfiguration.maxStoriesWeeklyCount,
|
maxStoriesWeeklyCount: userLimitsConfiguration.maxStoriesWeeklyCount,
|
||||||
maxStoriesMonthlyCount: userLimitsConfiguration.maxStoriesMonthlyCount,
|
maxStoriesMonthlyCount: userLimitsConfiguration.maxStoriesMonthlyCount,
|
||||||
maxStoriesSuggestedReactions: userLimitsConfiguration.maxStoriesSuggestedReactions,
|
maxStoriesSuggestedReactions: userLimitsConfiguration.maxStoriesSuggestedReactions,
|
||||||
maxGiveawayChannelsCount: userLimitsConfiguration.maxGiveawayChannelsCount
|
maxGiveawayChannelsCount: userLimitsConfiguration.maxGiveawayChannelsCount,
|
||||||
|
maxGiveawayCountriesCount: userLimitsConfiguration.maxGiveawayCountriesCount
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ public enum AppStoreTransactionPurpose {
|
|||||||
case restore
|
case restore
|
||||||
case gift(peerId: EnginePeer.Id, currency: String, amount: Int64)
|
case gift(peerId: EnginePeer.Id, currency: String, amount: Int64)
|
||||||
case giftCode(peerIds: [EnginePeer.Id], boostPeer: EnginePeer.Id?, currency: String, amount: Int64)
|
case giftCode(peerIds: [EnginePeer.Id], boostPeer: EnginePeer.Id?, currency: String, amount: Int64)
|
||||||
case giveaway(boostPeer: EnginePeer.Id, additionalPeerIds: [EnginePeer.Id], onlyNewSubscribers: Bool, randomId: Int64, untilDate: Int32, currency: String, amount: Int64)
|
case giveaway(boostPeer: EnginePeer.Id, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, randomId: Int64, untilDate: Int32, currency: String, amount: Int64)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func apiInputStorePaymentPurpose(account: Account, purpose: AppStoreTransactionPurpose) -> Signal<Api.InputStorePaymentPurpose, NoError> {
|
private func apiInputStorePaymentPurpose(account: Account, purpose: AppStoreTransactionPurpose) -> Signal<Api.InputStorePaymentPurpose, NoError> {
|
||||||
@ -59,7 +59,7 @@ private func apiInputStorePaymentPurpose(account: Account, purpose: AppStoreTran
|
|||||||
|
|
||||||
return .inputStorePaymentPremiumGiftCode(flags: flags, users: apiInputUsers, boostPeer: apiBoostPeer, currency: currency, amount: amount)
|
return .inputStorePaymentPremiumGiftCode(flags: flags, users: apiInputUsers, boostPeer: apiBoostPeer, currency: currency, amount: amount)
|
||||||
}
|
}
|
||||||
case let .giveaway(boostPeerId, additionalPeerIds, onlyNewSubscribers, randomId, untilDate, currency, amount):
|
case let .giveaway(boostPeerId, additionalPeerIds, countries, onlyNewSubscribers, randomId, untilDate, currency, amount):
|
||||||
return account.postbox.transaction { transaction -> Signal<Api.InputStorePaymentPurpose, NoError> in
|
return account.postbox.transaction { transaction -> Signal<Api.InputStorePaymentPurpose, NoError> in
|
||||||
guard let peer = transaction.getPeer(boostPeerId), let apiBoostPeer = apiInputPeer(peer) else {
|
guard let peer = transaction.getPeer(boostPeerId), let apiBoostPeer = apiInputPeer(peer) else {
|
||||||
return .complete()
|
return .complete()
|
||||||
@ -77,7 +77,10 @@ private func apiInputStorePaymentPurpose(account: Account, purpose: AppStoreTran
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return .single(.inputStorePaymentPremiumGiveaway(flags: flags, boostPeer: apiBoostPeer, additionalPeers: additionalPeers, countriesIso2: nil, randomId: randomId, untilDate: untilDate, currency: currency, amount: amount))
|
if !countries.isEmpty {
|
||||||
|
flags |= (1 << 2)
|
||||||
|
}
|
||||||
|
return .single(.inputStorePaymentPremiumGiveaway(flags: flags, boostPeer: apiBoostPeer, additionalPeers: additionalPeers, countriesIso2: countries, randomId: randomId, untilDate: untilDate, currency: currency, amount: amount))
|
||||||
}
|
}
|
||||||
|> switchToLatest
|
|> switchToLatest
|
||||||
}
|
}
|
||||||
|
@ -184,7 +184,7 @@ func _internal_applyPremiumGiftCode(account: Account, slug: String) -> Signal<Ne
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func _internal_launchPrepaidGiveaway(account: Account, peerId: EnginePeer.Id, id: Int64, additionalPeerIds: [EnginePeer.Id], onlyNewSubscribers: Bool, randomId: Int64, untilDate: Int32) -> Signal<Never, NoError> {
|
func _internal_launchPrepaidGiveaway(account: Account, peerId: EnginePeer.Id, id: Int64, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, randomId: Int64, untilDate: Int32) -> Signal<Never, NoError> {
|
||||||
return account.postbox.transaction { transaction -> Signal<Never, NoError> in
|
return account.postbox.transaction { transaction -> Signal<Never, NoError> in
|
||||||
var flags: Int32 = 0
|
var flags: Int32 = 0
|
||||||
if onlyNewSubscribers {
|
if onlyNewSubscribers {
|
||||||
@ -206,10 +206,14 @@ func _internal_launchPrepaidGiveaway(account: Account, peerId: EnginePeer.Id, id
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !countries.isEmpty {
|
||||||
|
flags |= (1 << 2)
|
||||||
|
}
|
||||||
|
|
||||||
guard let inputPeer = inputPeer else {
|
guard let inputPeer = inputPeer else {
|
||||||
return .complete()
|
return .complete()
|
||||||
}
|
}
|
||||||
return account.network.request(Api.functions.payments.launchPrepaidGiveaway(peer: inputPeer, giveawayId: id, purpose: .inputStorePaymentPremiumGiveaway(flags: flags, boostPeer: inputPeer, additionalPeers: additionalPeers, countriesIso2: nil, randomId: randomId, untilDate: untilDate, currency: "", amount: 0)))
|
return account.network.request(Api.functions.payments.launchPrepaidGiveaway(peer: inputPeer, giveawayId: id, purpose: .inputStorePaymentPremiumGiveaway(flags: flags, boostPeer: inputPeer, additionalPeers: additionalPeers, countriesIso2: countries, randomId: randomId, untilDate: untilDate, currency: "", amount: 0)))
|
||||||
|> map(Optional.init)
|
|> map(Optional.init)
|
||||||
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
|
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
|
||||||
return .single(nil)
|
return .single(nil)
|
||||||
|
@ -62,8 +62,8 @@ public extension TelegramEngine {
|
|||||||
return _internal_getPremiumGiveawayInfo(account: self.account, peerId: peerId, messageId: messageId)
|
return _internal_getPremiumGiveawayInfo(account: self.account, peerId: peerId, messageId: messageId)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func launchPrepaidGiveaway(peerId: EnginePeer.Id, id: Int64, additionalPeerIds: [EnginePeer.Id], onlyNewSubscribers: Bool, randomId: Int64, untilDate: Int32) -> Signal<Never, NoError> {
|
public func launchPrepaidGiveaway(peerId: EnginePeer.Id, id: Int64, additionalPeerIds: [EnginePeer.Id], countries: [String], onlyNewSubscribers: Bool, randomId: Int64, untilDate: Int32) -> Signal<Never, NoError> {
|
||||||
return _internal_launchPrepaidGiveaway(account: self.account, peerId: peerId, id: id, additionalPeerIds: additionalPeerIds, onlyNewSubscribers: onlyNewSubscribers, randomId: randomId, untilDate: untilDate)
|
return _internal_launchPrepaidGiveaway(account: self.account, peerId: peerId, id: id, additionalPeerIds: additionalPeerIds, countries: countries, onlyNewSubscribers: onlyNewSubscribers, randomId: randomId, untilDate: untilDate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,6 +54,7 @@ public final class CameraButton: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final class View: UIButton, ComponentTaggedView {
|
public final class View: UIButton, ComponentTaggedView {
|
||||||
|
private let containerView = UIView()
|
||||||
public var contentView: ComponentHostView<Empty>
|
public var contentView: ComponentHostView<Empty>
|
||||||
|
|
||||||
private var component: CameraButton?
|
private var component: CameraButton?
|
||||||
@ -75,12 +76,14 @@ public final class CameraButton: Component {
|
|||||||
} else {
|
} else {
|
||||||
scale = 1.0
|
scale = 1.0
|
||||||
}
|
}
|
||||||
transition.setScale(view: self, scale: scale)
|
transition.setScale(view: self.containerView, scale: scale)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var longTapGestureRecognizer: UILongPressGestureRecognizer?
|
private var longTapGestureRecognizer: UILongPressGestureRecognizer?
|
||||||
|
|
||||||
public override init(frame: CGRect) {
|
public override init(frame: CGRect) {
|
||||||
|
self.containerView.isUserInteractionEnabled = false
|
||||||
|
|
||||||
self.contentView = ComponentHostView<Empty>()
|
self.contentView = ComponentHostView<Empty>()
|
||||||
self.contentView.isUserInteractionEnabled = false
|
self.contentView.isUserInteractionEnabled = false
|
||||||
self.contentView.layer.allowsGroupOpacity = true
|
self.contentView.layer.allowsGroupOpacity = true
|
||||||
@ -89,7 +92,8 @@ public final class CameraButton: Component {
|
|||||||
|
|
||||||
self.isExclusiveTouch = true
|
self.isExclusiveTouch = true
|
||||||
|
|
||||||
self.addSubview(self.contentView)
|
self.addSubview(self.containerView)
|
||||||
|
self.containerView.addSubview(self.contentView)
|
||||||
|
|
||||||
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
||||||
|
|
||||||
@ -145,12 +149,12 @@ public final class CameraButton: Component {
|
|||||||
self.contentView = ComponentHostView<Empty>()
|
self.contentView = ComponentHostView<Empty>()
|
||||||
self.contentView.isUserInteractionEnabled = false
|
self.contentView.isUserInteractionEnabled = false
|
||||||
self.contentView.layer.allowsGroupOpacity = true
|
self.contentView.layer.allowsGroupOpacity = true
|
||||||
self.addSubview(self.contentView)
|
self.containerView.addSubview(self.contentView)
|
||||||
|
|
||||||
if transition.animation.isImmediate {
|
if transition.animation.isImmediate {
|
||||||
previousContentView.removeFromSuperview()
|
previousContentView.removeFromSuperview()
|
||||||
} else {
|
} else {
|
||||||
self.addSubview(previousContentView)
|
self.containerView.addSubview(previousContentView)
|
||||||
previousContentView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak previousContentView] _ in
|
previousContentView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak previousContentView] _ in
|
||||||
previousContentView?.removeFromSuperview()
|
previousContentView?.removeFromSuperview()
|
||||||
})
|
})
|
||||||
@ -175,7 +179,11 @@ public final class CameraButton: Component {
|
|||||||
self.isEnabled = component.isEnabled
|
self.isEnabled = component.isEnabled
|
||||||
self.longTapGestureRecognizer?.isEnabled = component.longTapAction != nil
|
self.longTapGestureRecognizer?.isEnabled = component.longTapAction != nil
|
||||||
|
|
||||||
self.contentView.frame = CGRect(origin: CGPoint(x: floor((size.width - contentSize.width) / 2.0), y: floor((size.height - contentSize.height) / 2.0)), size: contentSize)
|
self.contentView.bounds = CGRect(origin: .zero, size: contentSize)
|
||||||
|
self.contentView.center = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
|
||||||
|
|
||||||
|
self.containerView.bounds = CGRect(origin: .zero, size: size)
|
||||||
|
self.containerView.center = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
|
||||||
|
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
|
@ -80,6 +80,7 @@ swift_library(
|
|||||||
"//submodules/Utils/VolumeButtons",
|
"//submodules/Utils/VolumeButtons",
|
||||||
"//submodules/TelegramNotices",
|
"//submodules/TelegramNotices",
|
||||||
"//submodules/DeviceAccess",
|
"//submodules/DeviceAccess",
|
||||||
|
"//submodules/TelegramUI/Components/Utils/RoundedRectWithTailPath",
|
||||||
|
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
|
@ -30,16 +30,27 @@ enum CameraMode: Equatable {
|
|||||||
case video
|
case video
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct CameraState: Equatable {
|
struct CameraState: Equatable {
|
||||||
enum Recording: Equatable {
|
enum Recording: Equatable {
|
||||||
case none
|
case none
|
||||||
case holding
|
case holding
|
||||||
case handsFree
|
case handsFree
|
||||||
}
|
}
|
||||||
enum FlashTint {
|
enum FlashTint: Equatable {
|
||||||
case white
|
case white
|
||||||
case yellow
|
case yellow
|
||||||
case blue
|
case blue
|
||||||
|
|
||||||
|
var color: UIColor {
|
||||||
|
switch self {
|
||||||
|
case .white:
|
||||||
|
return .white
|
||||||
|
case .yellow:
|
||||||
|
return UIColor(rgb: 0xffed8c)
|
||||||
|
case .blue:
|
||||||
|
return UIColor(rgb: 0x8cdfff)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mode: CameraMode
|
let mode: CameraMode
|
||||||
@ -225,6 +236,8 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
var swipeHint: CaptureControlsComponent.SwipeHint = .none
|
var swipeHint: CaptureControlsComponent.SwipeHint = .none
|
||||||
var isTransitioning = false
|
var isTransitioning = false
|
||||||
|
|
||||||
|
var displayingFlashTint = false
|
||||||
|
|
||||||
private let hapticFeedback = HapticFeedback()
|
private let hapticFeedback = HapticFeedback()
|
||||||
|
|
||||||
init(
|
init(
|
||||||
@ -415,6 +428,27 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
self.hapticFeedback.impact(.light)
|
self.hapticFeedback.impact(.light)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateFlashTint(_ tint: CameraState.FlashTint?) {
|
||||||
|
guard let controller = self.getController(), let camera = controller.camera else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let tint {
|
||||||
|
controller.updateCameraState({ $0.updatedFlashTint(tint) }, transition: .easeInOut(duration: 0.2))
|
||||||
|
} else {
|
||||||
|
camera.setFlashMode(.off)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func presentFlashTint() {
|
||||||
|
guard let controller = self.getController(), let camera = controller.camera else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
camera.setFlashMode(.on)
|
||||||
|
|
||||||
|
self.displayingFlashTint = true
|
||||||
|
self.updated(transition: .immediate)
|
||||||
|
}
|
||||||
|
|
||||||
private var lastFlipTimestamp: Double?
|
private var lastFlipTimestamp: Double?
|
||||||
func togglePosition(_ action: ActionSlot<Void>) {
|
func togglePosition(_ action: ActionSlot<Void>) {
|
||||||
guard let controller = self.getController(), let camera = controller.camera else {
|
guard let controller = self.getController(), let camera = controller.camera else {
|
||||||
@ -622,6 +656,7 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
let dualButton = Child(CameraButton.self)
|
let dualButton = Child(CameraButton.self)
|
||||||
let modeControl = Child(ModeComponent.self)
|
let modeControl = Child(ModeComponent.self)
|
||||||
let hintLabel = Child(HintLabelComponent.self)
|
let hintLabel = Child(HintLabelComponent.self)
|
||||||
|
let flashTintControl = Child(FlashTintControlComponent.self)
|
||||||
|
|
||||||
let timeBackground = Child(RoundedRectangle.self)
|
let timeBackground = Child(RoundedRectangle.self)
|
||||||
let timeLabel = Child(MultilineTextComponent.self)
|
let timeLabel = Child(MultilineTextComponent.self)
|
||||||
@ -713,19 +748,11 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
// )
|
// )
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let displayFrontFlash = component.cameraState.recording != .none || component.cameraState.mode == .video || state.displayingFlashTint
|
||||||
var controlsTintColor: UIColor = .white
|
var controlsTintColor: UIColor = .white
|
||||||
if case .front = component.cameraState.position, case .on = component.cameraState.flashMode {
|
if case .front = component.cameraState.position, case .on = component.cameraState.flashMode, displayFrontFlash {
|
||||||
let flashTintColor: UIColor
|
|
||||||
switch component.cameraState.flashTint {
|
|
||||||
case .white:
|
|
||||||
flashTintColor = .white
|
|
||||||
case .yellow:
|
|
||||||
flashTintColor = UIColor(rgb: 0xffed8c)
|
|
||||||
case .blue:
|
|
||||||
flashTintColor = UIColor(rgb: 0x8cdfff)
|
|
||||||
}
|
|
||||||
let frontFlash = frontFlash.update(
|
let frontFlash = frontFlash.update(
|
||||||
component: Image(image: state.image(.flashImage), tintColor: flashTintColor),
|
component: Image(image: state.image(.flashImage), tintColor: component.cameraState.flashTint.color),
|
||||||
availableSize: availableSize,
|
availableSize: availableSize,
|
||||||
transition: .easeInOut(duration: 0.2)
|
transition: .easeInOut(duration: 0.2)
|
||||||
)
|
)
|
||||||
@ -849,6 +876,7 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
.position(captureControlsPosition)
|
.position(captureControlsPosition)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var flashButtonPosition: CGPoint?
|
||||||
let topControlInset: CGFloat = 20.0
|
let topControlInset: CGFloat = 20.0
|
||||||
if case .none = component.cameraState.recording, !state.isTransitioning {
|
if case .none = component.cameraState.recording, !state.isTransitioning {
|
||||||
let cancelButton = cancelButton.update(
|
let cancelButton = cancelButton.update(
|
||||||
@ -928,13 +956,21 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
if let state {
|
if let state {
|
||||||
state.toggleFlashMode()
|
state.toggleFlashMode()
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
longTapAction: { [weak state] in
|
||||||
|
if let state {
|
||||||
|
state.presentFlashTint()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
).tagged(flashButtonTag),
|
).tagged(flashButtonTag),
|
||||||
availableSize: CGSize(width: 40.0, height: 40.0),
|
availableSize: CGSize(width: 40.0, height: 40.0),
|
||||||
transition: .immediate
|
transition: .immediate
|
||||||
)
|
)
|
||||||
|
|
||||||
|
let position = CGPoint(x: isTablet ? availableSize.width - smallPanelWidth / 2.0 : availableSize.width - topControlInset - flashButton.size.width / 2.0 - 5.0, y: max(environment.statusBarHeight + 5.0, environment.safeInsets.top + topControlInset) + flashButton.size.height / 2.0)
|
||||||
|
flashButtonPosition = position
|
||||||
context.add(flashButton
|
context.add(flashButton
|
||||||
.position(CGPoint(x: isTablet ? availableSize.width - smallPanelWidth / 2.0 : availableSize.width - topControlInset - flashButton.size.width / 2.0 - 5.0, y: max(environment.statusBarHeight + 5.0, environment.safeInsets.top + topControlInset) + flashButton.size.height / 2.0))
|
.position(position)
|
||||||
.appear(.default(scale: true))
|
.appear(.default(scale: true))
|
||||||
.disappear(.default(scale: true))
|
.disappear(.default(scale: true))
|
||||||
)
|
)
|
||||||
@ -1114,6 +1150,28 @@ private final class CameraScreenComponent: CombinedComponent {
|
|||||||
.disappear(.default(alpha: true))
|
.disappear(.default(alpha: true))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let flashButtonPosition, state.displayingFlashTint {
|
||||||
|
let flashTintControl = flashTintControl.update(
|
||||||
|
component: FlashTintControlComponent(
|
||||||
|
position: flashButtonPosition.offsetBy(dx: 0.0, dy: 27.0),
|
||||||
|
tint: component.cameraState.flashTint,
|
||||||
|
update: { [weak state] tint in
|
||||||
|
state?.updateFlashTint(tint)
|
||||||
|
},
|
||||||
|
dismiss: { [weak state] in
|
||||||
|
state?.displayingFlashTint = false
|
||||||
|
state?.updated(transition: .easeInOut(duration: 0.2))
|
||||||
|
}
|
||||||
|
),
|
||||||
|
availableSize: availableSize,
|
||||||
|
transition: context.transition
|
||||||
|
)
|
||||||
|
context.add(flashTintControl
|
||||||
|
.position(CGPoint(x: availableSize.width / 2.0, y: availableSize.height / 2.0))
|
||||||
|
.disappear(.default(alpha: true))
|
||||||
|
)
|
||||||
|
}
|
||||||
return availableSize
|
return availableSize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2446,6 +2504,12 @@ public class CameraScreen: ViewController {
|
|||||||
if isTablet && isFirstTime {
|
if isTablet && isFirstTime {
|
||||||
self.animateIn()
|
self.animateIn()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.cameraState.flashMode == .on && (self.cameraState.recording != .none || self.cameraState.mode == .video) {
|
||||||
|
self.controller?.statusBarStyle = .Black
|
||||||
|
} else {
|
||||||
|
self.controller?.statusBarStyle = .White
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2743,13 +2807,12 @@ public class CameraScreen: ViewController {
|
|||||||
self.node.camera?.stopCapture(invalidate: true)
|
self.node.camera?.stopCapture(invalidate: true)
|
||||||
self.isDismissed = true
|
self.isDismissed = true
|
||||||
if animated {
|
if animated {
|
||||||
|
self.ignoreStatusBar = true
|
||||||
if let layout = self.validLayout, layout.metrics.isTablet {
|
if let layout = self.validLayout, layout.metrics.isTablet {
|
||||||
self.statusBar.updateStatusBarStyle(.Ignore, animated: true)
|
|
||||||
self.node.animateOut(completion: {
|
self.node.animateOut(completion: {
|
||||||
self.dismiss(animated: false)
|
self.dismiss(animated: false)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
self.statusBar.updateStatusBarStyle(.Ignore, animated: true)
|
|
||||||
if !interactive {
|
if !interactive {
|
||||||
if let navigationController = self.navigationController as? NavigationController {
|
if let navigationController = self.navigationController as? NavigationController {
|
||||||
navigationController.updateRootContainerTransitionOffset(self.node.frame.width, transition: .immediate)
|
navigationController.updateRootContainerTransitionOffset(self.node.frame.width, transition: .immediate)
|
||||||
@ -2810,16 +2873,41 @@ public class CameraScreen: ViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var statusBarStyle: StatusBarStyle = .White {
|
||||||
|
didSet {
|
||||||
|
if self.statusBarStyle != oldValue {
|
||||||
|
self.updateStatusBarAppearance()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private var ignoreStatusBar = false {
|
||||||
|
didSet {
|
||||||
|
if self.ignoreStatusBar != oldValue {
|
||||||
|
self.updateStatusBarAppearance()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateStatusBarAppearance() {
|
||||||
|
let effectiveStatusBarStyle: StatusBarStyle
|
||||||
|
if !self.ignoreStatusBar {
|
||||||
|
effectiveStatusBarStyle = self.statusBarStyle
|
||||||
|
} else {
|
||||||
|
effectiveStatusBarStyle = .Ignore
|
||||||
|
}
|
||||||
|
self.statusBar.updateStatusBarStyle(effectiveStatusBarStyle, animated: true)
|
||||||
|
}
|
||||||
|
|
||||||
public func completeWithTransitionProgress(_ transitionFraction: CGFloat, velocity: CGFloat, dismissing: Bool) {
|
public func completeWithTransitionProgress(_ transitionFraction: CGFloat, velocity: CGFloat, dismissing: Bool) {
|
||||||
if let layout = self.validLayout, layout.metrics.isTablet {
|
if let layout = self.validLayout, layout.metrics.isTablet {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if dismissing {
|
if dismissing {
|
||||||
if transitionFraction < 0.7 || velocity < -1000.0 {
|
if transitionFraction < 0.7 || velocity < -1000.0 {
|
||||||
self.statusBar.updateStatusBarStyle(.Ignore, animated: true)
|
self.ignoreStatusBar = true
|
||||||
self.requestDismiss(animated: true, interactive: true)
|
self.requestDismiss(animated: true, interactive: true)
|
||||||
} else {
|
} else {
|
||||||
self.statusBar.updateStatusBarStyle(.White, animated: true)
|
self.ignoreStatusBar = false
|
||||||
self.updateTransitionProgress(1.0, transition: .animated(duration: 0.4, curve: .spring), completion: { [weak self] in
|
self.updateTransitionProgress(1.0, transition: .animated(duration: 0.4, curve: .spring), completion: { [weak self] in
|
||||||
if let self, let navigationController = self.navigationController as? NavigationController {
|
if let self, let navigationController = self.navigationController as? NavigationController {
|
||||||
navigationController.updateRootContainerTransitionOffset(0.0, transition: .immediate)
|
navigationController.updateRootContainerTransitionOffset(0.0, transition: .immediate)
|
||||||
@ -2828,7 +2916,8 @@ public class CameraScreen: ViewController {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if transitionFraction > 0.33 || velocity > 1000.0 {
|
if transitionFraction > 0.33 || velocity > 1000.0 {
|
||||||
self.statusBar.updateStatusBarStyle(.White, animated: true)
|
self.ignoreStatusBar = false
|
||||||
|
self.updateStatusBarAppearance()
|
||||||
self.updateTransitionProgress(1.0, transition: .animated(duration: 0.4, curve: .spring), completion: { [weak self] in
|
self.updateTransitionProgress(1.0, transition: .animated(duration: 0.4, curve: .spring), completion: { [weak self] in
|
||||||
if let self, let navigationController = self.navigationController as? NavigationController {
|
if let self, let navigationController = self.navigationController as? NavigationController {
|
||||||
navigationController.updateRootContainerTransitionOffset(0.0, transition: .immediate)
|
navigationController.updateRootContainerTransitionOffset(0.0, transition: .immediate)
|
||||||
@ -2837,7 +2926,8 @@ public class CameraScreen: ViewController {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
self.statusBar.updateStatusBarStyle(.Ignore, animated: true)
|
self.ignoreStatusBar = true
|
||||||
|
self.updateStatusBarAppearance()
|
||||||
self.requestDismiss(animated: true, interactive: true)
|
self.requestDismiss(animated: true, interactive: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,300 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import Display
|
||||||
|
import ComponentFlow
|
||||||
|
import RoundedRectWithTailPath
|
||||||
|
|
||||||
|
private final class FlashColorComponent: Component {
|
||||||
|
let tint: CameraState.FlashTint?
|
||||||
|
let isSelected: Bool
|
||||||
|
let action: () -> Void
|
||||||
|
|
||||||
|
init(
|
||||||
|
tint: CameraState.FlashTint?,
|
||||||
|
isSelected: Bool,
|
||||||
|
action: @escaping () -> Void
|
||||||
|
) {
|
||||||
|
self.tint = tint
|
||||||
|
self.isSelected = isSelected
|
||||||
|
self.action = action
|
||||||
|
}
|
||||||
|
|
||||||
|
static func == (lhs: FlashColorComponent, rhs: FlashColorComponent) -> Bool {
|
||||||
|
return lhs.tint == rhs.tint && lhs.isSelected == rhs.isSelected
|
||||||
|
}
|
||||||
|
|
||||||
|
final class View: UIButton {
|
||||||
|
private var component: FlashColorComponent?
|
||||||
|
|
||||||
|
private var contentView: UIView
|
||||||
|
|
||||||
|
private let circleLayer: SimpleShapeLayer
|
||||||
|
private var ringLayer: CALayer?
|
||||||
|
private var iconLayer: CALayer?
|
||||||
|
|
||||||
|
private var currentIsHighlighted: Bool = false {
|
||||||
|
didSet {
|
||||||
|
if self.currentIsHighlighted != oldValue {
|
||||||
|
self.contentView.alpha = self.currentIsHighlighted ? 0.6 : 1.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
self.contentView = UIView(frame: CGRect(origin: .zero, size: frame.size))
|
||||||
|
self.contentView.isUserInteractionEnabled = false
|
||||||
|
self.circleLayer = SimpleShapeLayer()
|
||||||
|
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
self.addSubview(self.contentView)
|
||||||
|
self.contentView.layer.addSublayer(self.circleLayer)
|
||||||
|
|
||||||
|
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func pressed() {
|
||||||
|
self.component?.action()
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
|
||||||
|
self.currentIsHighlighted = true
|
||||||
|
|
||||||
|
return super.beginTracking(touch, with: event)
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func endTracking(_ touch: UITouch?, with event: UIEvent?) {
|
||||||
|
self.currentIsHighlighted = false
|
||||||
|
|
||||||
|
super.endTracking(touch, with: event)
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func cancelTracking(with event: UIEvent?) {
|
||||||
|
self.currentIsHighlighted = false
|
||||||
|
|
||||||
|
super.cancelTracking(with: event)
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(component: FlashColorComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
|
self.component = component
|
||||||
|
let contentSize = CGSize(width: 24.0, height: 24.0)
|
||||||
|
self.contentView.frame = CGRect(origin: .zero, size: contentSize)
|
||||||
|
|
||||||
|
let bounds = CGRect(origin: .zero, size: contentSize)
|
||||||
|
self.layer.allowsGroupOpacity = true
|
||||||
|
self.contentView.layer.allowsGroupOpacity = true
|
||||||
|
|
||||||
|
self.circleLayer.frame = bounds
|
||||||
|
if self.ringLayer == nil {
|
||||||
|
let ringLayer = SimpleLayer()
|
||||||
|
ringLayer.backgroundColor = UIColor.clear.cgColor
|
||||||
|
ringLayer.cornerRadius = contentSize.width / 2.0
|
||||||
|
ringLayer.borderWidth = 1.0 + UIScreenPixel
|
||||||
|
ringLayer.frame = CGRect(origin: .zero, size: contentSize)
|
||||||
|
self.contentView.layer.insertSublayer(ringLayer, at: 0)
|
||||||
|
self.ringLayer = ringLayer
|
||||||
|
}
|
||||||
|
|
||||||
|
if component.isSelected {
|
||||||
|
transition.setShapeLayerPath(layer: self.circleLayer, path: CGPath(ellipseIn: bounds.insetBy(dx: 3.0 - UIScreenPixel, dy: 3.0 - UIScreenPixel), transform: nil))
|
||||||
|
} else {
|
||||||
|
transition.setShapeLayerPath(layer: self.circleLayer, path: CGPath(ellipseIn: bounds, transform: nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
if let color = component.tint?.color {
|
||||||
|
self.circleLayer.fillColor = color.cgColor
|
||||||
|
self.ringLayer?.borderColor = color.cgColor
|
||||||
|
} else {
|
||||||
|
if self.iconLayer == nil {
|
||||||
|
let iconLayer = SimpleLayer()
|
||||||
|
iconLayer.contents = UIImage(bundleImageName: "Camera/FlashOffIcon")?.cgImage
|
||||||
|
iconLayer.contentsGravity = .resizeAspect
|
||||||
|
iconLayer.frame = bounds.insetBy(dx: -4.0, dy: -4.0)
|
||||||
|
self.contentView.layer.addSublayer(iconLayer)
|
||||||
|
self.iconLayer = iconLayer
|
||||||
|
}
|
||||||
|
|
||||||
|
self.circleLayer.fillColor = UIColor(rgb: 0xffffff, alpha: 0.1).cgColor
|
||||||
|
self.ringLayer?.borderColor = UIColor.clear.cgColor
|
||||||
|
}
|
||||||
|
|
||||||
|
return contentSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func makeView() -> View {
|
||||||
|
return View(frame: CGRect())
|
||||||
|
}
|
||||||
|
|
||||||
|
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
|
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class FlashTintControlComponent: Component {
|
||||||
|
let position: CGPoint
|
||||||
|
let tint: CameraState.FlashTint
|
||||||
|
let update: (CameraState.FlashTint?) -> Void
|
||||||
|
let dismiss: () -> Void
|
||||||
|
|
||||||
|
init(
|
||||||
|
position: CGPoint,
|
||||||
|
tint: CameraState.FlashTint,
|
||||||
|
update: @escaping (CameraState.FlashTint?) -> Void,
|
||||||
|
dismiss: @escaping () -> Void
|
||||||
|
) {
|
||||||
|
self.position = position
|
||||||
|
self.tint = tint
|
||||||
|
self.update = update
|
||||||
|
self.dismiss = dismiss
|
||||||
|
}
|
||||||
|
|
||||||
|
static func == (lhs: FlashTintControlComponent, rhs: FlashTintControlComponent) -> Bool {
|
||||||
|
return lhs.position == rhs.position && lhs.tint == rhs.tint
|
||||||
|
}
|
||||||
|
|
||||||
|
final class View: UIButton {
|
||||||
|
private var component: FlashTintControlComponent?
|
||||||
|
|
||||||
|
private let dismissView = UIView()
|
||||||
|
private let containerView = UIView()
|
||||||
|
private let effectView: UIVisualEffectView
|
||||||
|
private let maskLayer = CAShapeLayer()
|
||||||
|
private let swatches = ComponentView<Empty>()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
self.effectView = UIVisualEffectView(effect: UIBlurEffect(style: .dark))
|
||||||
|
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
self.containerView.layer.anchorPoint = CGPoint(x: 0.8, y: 0.0)
|
||||||
|
|
||||||
|
self.addSubview(self.dismissView)
|
||||||
|
self.addSubview(self.containerView)
|
||||||
|
self.containerView.addSubview(self.effectView)
|
||||||
|
|
||||||
|
self.dismissView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dismissTapped)))
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func dismissTapped() {
|
||||||
|
self.component?.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(component: FlashTintControlComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
|
let isFirstTime = self.component == nil
|
||||||
|
self.component = component
|
||||||
|
|
||||||
|
let size = CGSize(width: 160.0, height: 40.0)
|
||||||
|
if isFirstTime {
|
||||||
|
self.maskLayer.path = generateRoundedRectWithTailPath(rectSize: size, cornerRadius: 10.0, tailSize: CGSize(width: 18, height: 7.0), tailRadius: 1.0, tailPosition: 0.8, transformTail: false).cgPath
|
||||||
|
self.maskLayer.frame = CGRect(origin: .zero, size: CGSize(width: size.width, height: size.height + 7.0))
|
||||||
|
self.effectView.layer.mask = self.maskLayer
|
||||||
|
}
|
||||||
|
|
||||||
|
let swatchesSize = self.swatches.update(
|
||||||
|
transition: transition,
|
||||||
|
component: AnyComponent(
|
||||||
|
HStack(
|
||||||
|
[
|
||||||
|
AnyComponentWithIdentity(
|
||||||
|
id: "off",
|
||||||
|
component: AnyComponent(
|
||||||
|
FlashColorComponent(
|
||||||
|
tint: nil,
|
||||||
|
isSelected: false,
|
||||||
|
action: {
|
||||||
|
component.update(nil)
|
||||||
|
component.dismiss()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
AnyComponentWithIdentity(
|
||||||
|
id: "white",
|
||||||
|
component: AnyComponent(
|
||||||
|
FlashColorComponent(
|
||||||
|
tint: .white,
|
||||||
|
isSelected: component.tint == .white,
|
||||||
|
action: {
|
||||||
|
component.update(.white)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
AnyComponentWithIdentity(
|
||||||
|
id: "yellow",
|
||||||
|
component: AnyComponent(
|
||||||
|
FlashColorComponent(
|
||||||
|
tint: .yellow,
|
||||||
|
isSelected: component.tint == .yellow,
|
||||||
|
action: {
|
||||||
|
component.update(.yellow)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
AnyComponentWithIdentity(
|
||||||
|
id: "blue",
|
||||||
|
component: AnyComponent(
|
||||||
|
FlashColorComponent(
|
||||||
|
tint: .blue,
|
||||||
|
isSelected: component.tint == .blue,
|
||||||
|
action: {
|
||||||
|
component.update(.blue)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
],
|
||||||
|
spacing: 16.0
|
||||||
|
)
|
||||||
|
),
|
||||||
|
environment: {},
|
||||||
|
containerSize: availableSize
|
||||||
|
)
|
||||||
|
if let view = self.swatches.view {
|
||||||
|
if view.superview == nil {
|
||||||
|
self.containerView.addSubview(view)
|
||||||
|
}
|
||||||
|
view.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - swatchesSize.width) / 2.0), y: floorToScreenPixels((size.height - swatchesSize.height) / 2.0)), size: swatchesSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.dismissView.frame = CGRect(origin: .zero, size: availableSize)
|
||||||
|
|
||||||
|
self.containerView.bounds = CGRect(origin: .zero, size: size)
|
||||||
|
self.containerView.center = component.position
|
||||||
|
|
||||||
|
self.effectView.frame = CGRect(origin: CGPoint(x: 0.0, y: -7.0), size: CGSize(width: size.width, height: size.height + 7.0))
|
||||||
|
|
||||||
|
if isFirstTime {
|
||||||
|
self.containerView.layer.animateScale(from: 0.0, to: 1.0, duration: 0.35, timingFunction: kCAMediaTimingFunctionSpring)
|
||||||
|
self.containerView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
|
||||||
|
}
|
||||||
|
|
||||||
|
return availableSize
|
||||||
|
}
|
||||||
|
|
||||||
|
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||||
|
if !self.containerView.frame.contains(point) {
|
||||||
|
return self.dismissView
|
||||||
|
}
|
||||||
|
return super.hitTest(point, with: event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func makeView() -> View {
|
||||||
|
return View(frame: CGRect())
|
||||||
|
}
|
||||||
|
|
||||||
|
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
|
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
@ -27,10 +27,12 @@ final class MediaNavigationStripComponent: Component {
|
|||||||
|
|
||||||
let index: Int
|
let index: Int
|
||||||
let count: Int
|
let count: Int
|
||||||
|
let isSeeking: Bool
|
||||||
|
|
||||||
init(index: Int, count: Int) {
|
init(index: Int, count: Int, isSeeking: Bool) {
|
||||||
self.index = index
|
self.index = index
|
||||||
self.count = count
|
self.count = count
|
||||||
|
self.isSeeking = isSeeking
|
||||||
}
|
}
|
||||||
|
|
||||||
static func ==(lhs: MediaNavigationStripComponent, rhs: MediaNavigationStripComponent) -> Bool {
|
static func ==(lhs: MediaNavigationStripComponent, rhs: MediaNavigationStripComponent) -> Bool {
|
||||||
@ -40,6 +42,9 @@ final class MediaNavigationStripComponent: Component {
|
|||||||
if lhs.count != rhs.count {
|
if lhs.count != rhs.count {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if lhs.isSeeking != rhs.isSeeking {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,12 +160,18 @@ final class MediaNavigationStripComponent: Component {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
guard !self.isTransitioning else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
let itemFrame = itemLayer.bounds
|
let itemFrame = itemLayer.bounds
|
||||||
transition.setFrame(layer: itemLayer.foregroundLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: value * itemFrame.width, height: itemFrame.height)))
|
transition.setFrame(layer: itemLayer.foregroundLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: value * itemFrame.width, height: itemFrame.height)))
|
||||||
itemLayer.updateIsBuffering(size: itemFrame.size, isBuffering: isBuffering)
|
itemLayer.updateIsBuffering(size: itemFrame.size, isBuffering: isBuffering)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var isTransitioning = false
|
||||||
func update(component: MediaNavigationStripComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
func update(component: MediaNavigationStripComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||||
|
let previousComponent = self.component
|
||||||
self.component = component
|
self.component = component
|
||||||
|
|
||||||
let environment = environment[EnvironmentType.self].value
|
let environment = environment[EnvironmentType.self].value
|
||||||
@ -169,6 +180,10 @@ final class MediaNavigationStripComponent: Component {
|
|||||||
let itemHeight: CGFloat = 2.0
|
let itemHeight: CGFloat = 2.0
|
||||||
let minItemWidth: CGFloat = 2.0
|
let minItemWidth: CGFloat = 2.0
|
||||||
|
|
||||||
|
var size = CGSize(width: availableSize.width, height: itemHeight)
|
||||||
|
|
||||||
|
var didSetCompletion = false
|
||||||
|
|
||||||
var validIndices: [Int] = []
|
var validIndices: [Int] = []
|
||||||
if component.count != 0 {
|
if component.count != 0 {
|
||||||
let idealItemWidth: CGFloat = (availableSize.width - CGFloat(component.count - 1) * spacing) / CGFloat(component.count)
|
let idealItemWidth: CGFloat = (availableSize.width - CGFloat(component.count - 1) * spacing) / CGFloat(component.count)
|
||||||
@ -205,8 +220,12 @@ final class MediaNavigationStripComponent: Component {
|
|||||||
if i >= component.count {
|
if i >= component.count {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
let itemFrame = CGRect(origin: CGPoint(x: -globalOffset + CGFloat(i) * (itemWidth + spacing), y: 0.0), size: CGSize(width: itemWidth, height: itemHeight))
|
var itemFrame = CGRect(origin: CGPoint(x: -globalOffset + CGFloat(i) * (itemWidth + spacing), y: 0.0), size: CGSize(width: itemWidth, height: itemHeight))
|
||||||
if itemFrame.maxY < 0.0 || itemFrame.minY >= availableSize.width {
|
if component.isSeeking {
|
||||||
|
itemFrame = CGRect(origin: .zero, size: CGSize(width: availableSize.width, height: 6.0))
|
||||||
|
size.height = itemFrame.height
|
||||||
|
}
|
||||||
|
if itemFrame.maxX < 0.0 || itemFrame.minX >= availableSize.width {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -219,10 +238,20 @@ final class MediaNavigationStripComponent: Component {
|
|||||||
itemLayer = ItemLayer()
|
itemLayer = ItemLayer()
|
||||||
self.layer.addSublayer(itemLayer)
|
self.layer.addSublayer(itemLayer)
|
||||||
self.visibleItems[i] = itemLayer
|
self.visibleItems[i] = itemLayer
|
||||||
itemLayer.cornerRadius = itemHeight * 0.5
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
transition.setFrame(layer: itemLayer, frame: itemFrame)
|
transition.setFrame(layer: itemLayer, frame: itemFrame)
|
||||||
|
transition.setCornerRadius(layer: itemLayer, cornerRadius: itemFrame.height * 0.5)
|
||||||
|
transition.setCornerRadius(layer: itemLayer.foregroundLayer, cornerRadius: itemFrame.height * 0.5, completion: transition.animation.isImmediate || didSetCompletion ? nil : { [weak self] _ in
|
||||||
|
if let self {
|
||||||
|
self.isTransitioning = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if !transition.animation.isImmediate && component.isSeeking != previousComponent?.isSeeking {
|
||||||
|
self.isTransitioning = true
|
||||||
|
didSetCompletion = true
|
||||||
|
}
|
||||||
|
|
||||||
itemLayer.backgroundColor = UIColor(white: 1.0, alpha: 0.5).cgColor
|
itemLayer.backgroundColor = UIColor(white: 1.0, alpha: 0.5).cgColor
|
||||||
itemLayer.foregroundLayer.backgroundColor = UIColor(white: 1.0, alpha: 1.0).cgColor
|
itemLayer.foregroundLayer.backgroundColor = UIColor(white: 1.0, alpha: 1.0).cgColor
|
||||||
@ -239,6 +268,9 @@ final class MediaNavigationStripComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
transition.setFrame(layer: itemLayer.foregroundLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: itemProgress * itemFrame.width, height: itemFrame.height)))
|
transition.setFrame(layer: itemLayer.foregroundLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: itemProgress * itemFrame.width, height: itemFrame.height)))
|
||||||
|
|
||||||
|
transition.setAlpha(layer: itemLayer, alpha: !component.isSeeking || i == component.index ? 1.0 : 0.0)
|
||||||
|
|
||||||
itemLayer.updateIsBuffering(size: itemFrame.size, isBuffering: itemIsBuffering)
|
itemLayer.updateIsBuffering(size: itemFrame.size, isBuffering: itemIsBuffering)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,6 +92,8 @@ private final class MuteMonitor {
|
|||||||
private final class StoryLongPressRecognizer: UILongPressGestureRecognizer {
|
private final class StoryLongPressRecognizer: UILongPressGestureRecognizer {
|
||||||
var shouldBegin: ((UITouch) -> Bool)?
|
var shouldBegin: ((UITouch) -> Bool)?
|
||||||
var updateIsTracking: ((CGPoint?) -> Void)?
|
var updateIsTracking: ((CGPoint?) -> Void)?
|
||||||
|
var updatePanMove: ((CGPoint, CGPoint) -> Void)?
|
||||||
|
var updatePanEnded: (() -> Void)?
|
||||||
|
|
||||||
override var state: UIGestureRecognizer.State {
|
override var state: UIGestureRecognizer.State {
|
||||||
didSet {
|
didSet {
|
||||||
@ -110,6 +112,8 @@ private final class StoryLongPressRecognizer: UILongPressGestureRecognizer {
|
|||||||
private var isTracking: Bool = false
|
private var isTracking: Bool = false
|
||||||
private var isValidated: Bool = false
|
private var isValidated: Bool = false
|
||||||
|
|
||||||
|
private var initialLocation: CGPoint?
|
||||||
|
|
||||||
override func reset() {
|
override func reset() {
|
||||||
super.reset()
|
super.reset()
|
||||||
|
|
||||||
@ -134,10 +138,33 @@ private final class StoryLongPressRecognizer: UILongPressGestureRecognizer {
|
|||||||
|
|
||||||
if !self.isTracking {
|
if !self.isTracking {
|
||||||
self.isTracking = true
|
self.isTracking = true
|
||||||
self.updateIsTracking?(touches.first?.location(in: self.view))
|
self.initialLocation = touches.first?.location(in: self.view)
|
||||||
|
self.updateIsTracking?(initialLocation)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||||
|
if self.isValidated {
|
||||||
|
super.touchesMoved(touches, with: event)
|
||||||
|
|
||||||
|
if let location = touches.first?.location(in: self.view), let initialLocation = self.initialLocation {
|
||||||
|
self.updatePanMove?(initialLocation, CGPoint(x: location.x - initialLocation.x, y: location.y - initialLocation.y))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||||
|
super.touchesEnded(touches, with: event)
|
||||||
|
|
||||||
|
self.updatePanEnded?()
|
||||||
|
}
|
||||||
|
|
||||||
|
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||||
|
super.touchesCancelled(touches, with: event)
|
||||||
|
|
||||||
|
self.updatePanEnded?()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class StoryPinchGesture: UIPinchGestureRecognizer {
|
private final class StoryPinchGesture: UIPinchGestureRecognizer {
|
||||||
@ -397,6 +424,9 @@ private final class StoryContainerScreenComponent: Component {
|
|||||||
private var isDisplayingInteractionGuide: Bool = false
|
private var isDisplayingInteractionGuide: Bool = false
|
||||||
private var displayInteractionGuideDisposable: Disposable?
|
private var displayInteractionGuideDisposable: Disposable?
|
||||||
|
|
||||||
|
private var previousSeekTime: Double?
|
||||||
|
private var initialSeekTimestamp: Double?
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
self.backgroundLayer = SimpleLayer()
|
self.backgroundLayer = SimpleLayer()
|
||||||
self.backgroundLayer.backgroundColor = UIColor.black.cgColor
|
self.backgroundLayer.backgroundColor = UIColor.black.cgColor
|
||||||
@ -468,6 +498,48 @@ private final class StoryContainerScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
longPressRecognizer.updatePanMove = { [weak self] initialLocation, translation in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let stateValue = self.stateValue, let slice = stateValue.slice, let itemSetView = self.visibleItemSetViews[slice.peer.id], let itemSetComponentView = itemSetView.view.view as? StoryItemSetContainerComponent.View else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let visibleItemView = itemSetComponentView.visibleItems[slice.item.storyItem.id]?.view.view as? StoryItemContentComponent.View else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentTime = CACurrentMediaTime()
|
||||||
|
if let previousTime = self.previousSeekTime, currentTime - previousTime < 0.1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.previousSeekTime = currentTime
|
||||||
|
|
||||||
|
let initialSeekTimestamp: Double
|
||||||
|
if let current = self.initialSeekTimestamp {
|
||||||
|
initialSeekTimestamp = current
|
||||||
|
} else {
|
||||||
|
initialSeekTimestamp = visibleItemView.effectiveTimestamp
|
||||||
|
self.initialSeekTimestamp = initialSeekTimestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
let timestamp: Double
|
||||||
|
if translation.x > 0.0 {
|
||||||
|
let fraction = translation.x / (self.bounds.width - initialLocation.x)
|
||||||
|
timestamp = initialSeekTimestamp + (visibleItemView.effectiveDuration - initialSeekTimestamp) * fraction * fraction
|
||||||
|
} else {
|
||||||
|
let fraction = translation.x / initialLocation.x
|
||||||
|
timestamp = initialSeekTimestamp + initialSeekTimestamp * fraction * fraction * -1.0
|
||||||
|
}
|
||||||
|
visibleItemView.seekTo(timestamp)
|
||||||
|
}
|
||||||
|
longPressRecognizer.updatePanEnded = { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.initialSeekTimestamp = nil
|
||||||
|
self.previousSeekTime = nil
|
||||||
|
}
|
||||||
longPressRecognizer.shouldBegin = { [weak self] touch in
|
longPressRecognizer.shouldBegin = { [weak self] touch in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return false
|
return false
|
||||||
|
@ -397,6 +397,25 @@ final class StoryItemContentComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var effectiveTimestamp: Double {
|
||||||
|
guard let videoPlaybackStatus = self.videoPlaybackStatus else {
|
||||||
|
return 0.0
|
||||||
|
}
|
||||||
|
return videoPlaybackStatus.timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
var effectiveDuration: Double {
|
||||||
|
let effectiveDuration: Double
|
||||||
|
if let videoPlaybackStatus, videoPlaybackStatus.duration > 0.0 {
|
||||||
|
effectiveDuration = videoPlaybackStatus.duration
|
||||||
|
} else if case let .file(file) = self.currentMessageMedia, let duration = file.duration {
|
||||||
|
effectiveDuration = Double(max(1, duration))
|
||||||
|
} else {
|
||||||
|
effectiveDuration = 1.0
|
||||||
|
}
|
||||||
|
return effectiveDuration
|
||||||
|
}
|
||||||
|
|
||||||
private func updateVideoPlaybackProgress() {
|
private func updateVideoPlaybackProgress() {
|
||||||
guard let videoPlaybackStatus = self.videoPlaybackStatus else {
|
guard let videoPlaybackStatus = self.videoPlaybackStatus else {
|
||||||
return
|
return
|
||||||
@ -510,6 +529,14 @@ final class StoryItemContentComponent: Component {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func seekTo(_ timestamp: Double) {
|
||||||
|
guard let videoNode = self.videoNode else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
videoNode.seek(timestamp)
|
||||||
|
self.updateVideoPlaybackProgress()
|
||||||
|
}
|
||||||
|
|
||||||
func update(component: StoryItemContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<StoryContentItem.Environment>, transition: Transition) -> CGSize {
|
func update(component: StoryItemContentComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<StoryContentItem.Environment>, transition: Transition) -> CGSize {
|
||||||
let previousItem = self.component?.item
|
let previousItem = self.component?.item
|
||||||
|
|
||||||
|
@ -403,6 +403,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
let itemsContainerView: UIView
|
let itemsContainerView: UIView
|
||||||
let controlsContainerView: UIView
|
let controlsContainerView: UIView
|
||||||
let controlsClippingView: UIView
|
let controlsClippingView: UIView
|
||||||
|
let controlsNavigationClippingView: UIView
|
||||||
let topContentGradientView: UIImageView
|
let topContentGradientView: UIImageView
|
||||||
let bottomContentGradientLayer: SimpleGradientLayer
|
let bottomContentGradientLayer: SimpleGradientLayer
|
||||||
let contentDimView: UIView
|
let contentDimView: UIView
|
||||||
@ -411,6 +412,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
let closeButtonIconView: UIImageView
|
let closeButtonIconView: UIImageView
|
||||||
|
|
||||||
let navigationStrip = ComponentView<MediaNavigationStripComponent.EnvironmentType>()
|
let navigationStrip = ComponentView<MediaNavigationStripComponent.EnvironmentType>()
|
||||||
|
let seekLabel = ComponentView<Empty>()
|
||||||
|
|
||||||
var centerInfoItem: InfoItem?
|
var centerInfoItem: InfoItem?
|
||||||
var leftInfoItem: InfoItem?
|
var leftInfoItem: InfoItem?
|
||||||
@ -508,6 +510,12 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
self.controlsClippingView.layer.cornerCurve = .continuous
|
self.controlsClippingView.layer.cornerCurve = .continuous
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.controlsNavigationClippingView = SparseContainerView()
|
||||||
|
self.controlsNavigationClippingView.clipsToBounds = true
|
||||||
|
if #available(iOS 13.0, *) {
|
||||||
|
self.controlsNavigationClippingView.layer.cornerCurve = .continuous
|
||||||
|
}
|
||||||
|
|
||||||
self.topContentGradientView = UIImageView()
|
self.topContentGradientView = UIImageView()
|
||||||
if let image = StoryItemSetContainerComponent.shadowImage {
|
if let image = StoryItemSetContainerComponent.shadowImage {
|
||||||
self.topContentGradientView.image = image.stretchableImage(withLeftCapWidth: 0, topCapHeight: Int(image.size.height - 1.0))
|
self.topContentGradientView.image = image.stretchableImage(withLeftCapWidth: 0, topCapHeight: Int(image.size.height - 1.0))
|
||||||
@ -539,11 +547,12 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
self.itemsContainerView.addGestureRecognizer(self.scroller.panGestureRecognizer)
|
self.itemsContainerView.addGestureRecognizer(self.scroller.panGestureRecognizer)
|
||||||
|
|
||||||
self.componentContainerView.addSubview(self.itemsContainerView)
|
self.componentContainerView.addSubview(self.itemsContainerView)
|
||||||
|
self.componentContainerView.addSubview(self.controlsNavigationClippingView)
|
||||||
self.componentContainerView.addSubview(self.controlsClippingView)
|
self.componentContainerView.addSubview(self.controlsClippingView)
|
||||||
self.componentContainerView.addSubview(self.controlsContainerView)
|
self.componentContainerView.addSubview(self.controlsContainerView)
|
||||||
|
|
||||||
self.controlsClippingView.addSubview(self.contentDimView)
|
self.controlsClippingView.addSubview(self.contentDimView)
|
||||||
self.controlsClippingView.addSubview(self.topContentGradientView)
|
self.controlsNavigationClippingView.addSubview(self.topContentGradientView)
|
||||||
self.layer.addSublayer(self.bottomContentGradientLayer)
|
self.layer.addSublayer(self.bottomContentGradientLayer)
|
||||||
|
|
||||||
self.componentContainerView.addSubview(self.viewListsContainer)
|
self.componentContainerView.addSubview(self.viewListsContainer)
|
||||||
@ -2054,6 +2063,9 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
duration: 0.3
|
duration: 0.3
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.controlsNavigationClippingView.layer.animatePosition(from: sourceLocalFrame.center, to: self.controlsNavigationClippingView.center, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
||||||
|
self.controlsNavigationClippingView.layer.animateBounds(from: CGRect(origin: CGPoint(x: innerSourceLocalFrame.minX, y: innerSourceLocalFrame.minY), size: sourceLocalFrame.size), to: self.controlsNavigationClippingView.bounds, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
||||||
|
|
||||||
if let component = self.component, let visibleItemView = self.visibleItems[component.slice.item.storyItem.id]?.view.view {
|
if let component = self.component, let visibleItemView = self.visibleItems[component.slice.item.storyItem.id]?.view.view {
|
||||||
let innerScale = innerSourceLocalFrame.width / visibleItemView.bounds.width
|
let innerScale = innerSourceLocalFrame.width / visibleItemView.bounds.width
|
||||||
let innerFromFrame = CGRect(origin: CGPoint(x: innerSourceLocalFrame.minX, y: innerSourceLocalFrame.minY), size: CGSize(width: innerSourceLocalFrame.width, height: visibleItemView.bounds.height * innerScale))
|
let innerFromFrame = CGRect(origin: CGPoint(x: innerSourceLocalFrame.minX, y: innerSourceLocalFrame.minY), size: CGSize(width: innerSourceLocalFrame.width, height: visibleItemView.bounds.height * innerScale))
|
||||||
@ -2264,6 +2276,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
unclippedContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
unclippedContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
||||||
self.controlsContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
self.controlsContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
||||||
self.controlsClippingView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
self.controlsClippingView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
||||||
|
self.controlsNavigationClippingView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
||||||
|
|
||||||
for transitionViewImpl in transitionViewsImpl {
|
for transitionViewImpl in transitionViewsImpl {
|
||||||
transition.setFrame(view: transitionViewImpl, frame: sourceLocalFrame)
|
transition.setFrame(view: transitionViewImpl, frame: sourceLocalFrame)
|
||||||
@ -2318,6 +2331,9 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
removeOnCompletion: false
|
removeOnCompletion: false
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.controlsNavigationClippingView.layer.animatePosition(from: self.controlsNavigationClippingView.center, to: sourceLocalFrame.center, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||||
|
self.controlsNavigationClippingView.layer.animateBounds(from: self.controlsNavigationClippingView.bounds, to: CGRect(origin: CGPoint(x: innerSourceLocalFrame.minX, y: innerSourceLocalFrame.minY), size: sourceLocalFrame.size), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false)
|
||||||
|
|
||||||
self.overlayContainerView.clipsToBounds = true
|
self.overlayContainerView.clipsToBounds = true
|
||||||
let overlayToFrame = sourceLocalFrame
|
let overlayToFrame = sourceLocalFrame
|
||||||
let overlayToBounds = CGRect(origin: CGPoint(x: overlayToFrame.minX, y: overlayToFrame.minY), size: overlayToFrame.size)
|
let overlayToBounds = CGRect(origin: CGPoint(x: overlayToFrame.minX, y: overlayToFrame.minY), size: overlayToFrame.size)
|
||||||
@ -2382,6 +2398,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
unclippedContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
unclippedContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||||
self.controlsContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
self.controlsContainerView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||||
self.controlsClippingView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
self.controlsClippingView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||||
|
self.controlsNavigationClippingView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||||
|
|
||||||
for transitionViewImpl in transitionViewsImpl {
|
for transitionViewImpl in transitionViewsImpl {
|
||||||
transition.setFrame(view: transitionViewImpl, frame: sourceLocalFrame)
|
transition.setFrame(view: transitionViewImpl, frame: sourceLocalFrame)
|
||||||
@ -2523,6 +2540,8 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
#endif*/
|
#endif*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let previousComponent = self.component
|
||||||
|
|
||||||
var isFirstItem = false
|
var isFirstItem = false
|
||||||
var itemChanged = false
|
var itemChanged = false
|
||||||
var resetInputContents: MessageInputPanelComponent.SendMessageInput?
|
var resetInputContents: MessageInputPanelComponent.SendMessageInput?
|
||||||
@ -3603,6 +3622,9 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
transition.setPosition(view: self.controlsClippingView, position: contentFrame.center)
|
transition.setPosition(view: self.controlsClippingView, position: contentFrame.center)
|
||||||
transition.setBounds(view: self.controlsClippingView, bounds: CGRect(origin: CGPoint(), size: contentFrame.size))
|
transition.setBounds(view: self.controlsClippingView, bounds: CGRect(origin: CGPoint(), size: contentFrame.size))
|
||||||
|
|
||||||
|
transition.setPosition(view: self.controlsNavigationClippingView, position: contentFrame.center)
|
||||||
|
transition.setBounds(view: self.controlsNavigationClippingView, bounds: CGRect(origin: CGPoint(), size: contentFrame.size))
|
||||||
|
|
||||||
var transform = CATransform3DMakeScale(contentVisualScale, contentVisualScale, 1.0)
|
var transform = CATransform3DMakeScale(contentVisualScale, contentVisualScale, 1.0)
|
||||||
if let pinchState = component.pinchState {
|
if let pinchState = component.pinchState {
|
||||||
let pinchOffset = CGPoint(
|
let pinchOffset = CGPoint(
|
||||||
@ -3619,6 +3641,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
}
|
}
|
||||||
transition.setTransform(view: self.controlsContainerView, transform: transform)
|
transition.setTransform(view: self.controlsContainerView, transform: transform)
|
||||||
transition.setTransform(view: self.controlsClippingView, transform: transform)
|
transition.setTransform(view: self.controlsClippingView, transform: transform)
|
||||||
|
transition.setTransform(view: self.controlsNavigationClippingView, transform: transform)
|
||||||
|
|
||||||
transition.setCornerRadius(layer: self.controlsClippingView.layer, cornerRadius: 12.0 * (1.0 / contentVisualScale))
|
transition.setCornerRadius(layer: self.controlsClippingView.layer, cornerRadius: 12.0 * (1.0 / contentVisualScale))
|
||||||
|
|
||||||
@ -3870,6 +3893,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
let controlsContainerAlpha = (component.hideUI || self.isEditingStory || self.viewListDisplayState != .hidden) ? 0.0 : 1.0
|
let controlsContainerAlpha = (component.hideUI || self.isEditingStory || self.viewListDisplayState != .hidden) ? 0.0 : 1.0
|
||||||
transition.setAlpha(view: self.controlsContainerView, alpha: controlsContainerAlpha)
|
transition.setAlpha(view: self.controlsContainerView, alpha: controlsContainerAlpha)
|
||||||
transition.setAlpha(view: self.controlsClippingView, alpha: controlsContainerAlpha)
|
transition.setAlpha(view: self.controlsClippingView, alpha: controlsContainerAlpha)
|
||||||
|
transition.setAlpha(view: self.controlsNavigationClippingView, alpha: self.isEditingStory || self.viewListDisplayState != .hidden ? 0.0 : 1.0)
|
||||||
|
|
||||||
let focusedItem: StoryContentItem? = component.slice.item
|
let focusedItem: StoryContentItem? = component.slice.item
|
||||||
|
|
||||||
@ -4584,7 +4608,7 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
//transition.setAlpha(layer: self.bottomContentGradientLayer, alpha: inputPanelIsOverlay ? 1.0 : 0.0)
|
//transition.setAlpha(layer: self.bottomContentGradientLayer, alpha: inputPanelIsOverlay ? 1.0 : 0.0)
|
||||||
transition.setAlpha(layer: self.bottomContentGradientLayer, alpha: 0.0)
|
transition.setAlpha(layer: self.bottomContentGradientLayer, alpha: 0.0)
|
||||||
|
|
||||||
var topGradientAlpha: CGFloat = (component.hideUI || self.viewListDisplayState != .hidden || self.isEditingStory) ? 0.0 : 1.0
|
var topGradientAlpha: CGFloat = (self.viewListDisplayState != .hidden || self.isEditingStory) ? 0.0 : 1.0
|
||||||
var normalDimAlpha: CGFloat = 0.0
|
var normalDimAlpha: CGFloat = 0.0
|
||||||
var forceDimAnimation = false
|
var forceDimAnimation = false
|
||||||
if let captionItem = self.captionItem {
|
if let captionItem = self.captionItem {
|
||||||
@ -4644,10 +4668,9 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
|
|
||||||
let startTime9 = CFAbsoluteTimeGetCurrent()
|
let startTime9 = CFAbsoluteTimeGetCurrent()
|
||||||
|
|
||||||
if let focusedItem, let visibleItem = self.visibleItems[focusedItem.storyItem.id], let index = focusedItem.position {
|
|
||||||
let navigationStripSideInset: CGFloat = 8.0
|
let navigationStripSideInset: CGFloat = 8.0
|
||||||
let navigationStripTopInset: CGFloat = 8.0
|
let navigationStripTopInset: CGFloat = 8.0
|
||||||
|
if let focusedItem, let visibleItem = self.visibleItems[focusedItem.storyItem.id], let index = focusedItem.position {
|
||||||
var index = max(0, min(index, component.slice.totalCount - 1))
|
var index = max(0, min(index, component.slice.totalCount - 1))
|
||||||
var count = component.slice.totalCount
|
var count = component.slice.totalCount
|
||||||
if let dayCounters = focusedItem.dayCounters {
|
if let dayCounters = focusedItem.dayCounters {
|
||||||
@ -4655,11 +4678,18 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
count = dayCounters.totalCount
|
count = dayCounters.totalCount
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = self.navigationStrip.update(
|
let isSeeking = component.isProgressPaused && component.hideUI
|
||||||
transition: transition,
|
|
||||||
|
var navigationStripTransition = transition
|
||||||
|
if let previousComponent, (previousComponent.isProgressPaused && component.hideUI) != isSeeking {
|
||||||
|
navigationStripTransition = .easeInOut(duration: 0.3)
|
||||||
|
}
|
||||||
|
let navigationStripSize = self.navigationStrip.update(
|
||||||
|
transition: navigationStripTransition,
|
||||||
component: AnyComponent(MediaNavigationStripComponent(
|
component: AnyComponent(MediaNavigationStripComponent(
|
||||||
index: index,
|
index: index,
|
||||||
count: count
|
count: count,
|
||||||
|
isSeeking: isSeeking
|
||||||
)),
|
)),
|
||||||
environment: {
|
environment: {
|
||||||
MediaNavigationStripComponent.EnvironmentType(
|
MediaNavigationStripComponent.EnvironmentType(
|
||||||
@ -4667,16 +4697,34 @@ public final class StoryItemSetContainerComponent: Component {
|
|||||||
currentIsBuffering: visibleItem.isBuffering
|
currentIsBuffering: visibleItem.isBuffering
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
containerSize: CGSize(width: availableSize.width - navigationStripSideInset * 2.0, height: 2.0)
|
containerSize: CGSize(width: availableSize.width - navigationStripSideInset * 2.0, height: 6.0)
|
||||||
)
|
)
|
||||||
if let navigationStripView = self.navigationStrip.view {
|
if let navigationStripView = self.navigationStrip.view {
|
||||||
if navigationStripView.superview == nil {
|
if navigationStripView.superview == nil {
|
||||||
navigationStripView.isUserInteractionEnabled = false
|
navigationStripView.isUserInteractionEnabled = false
|
||||||
self.controlsClippingView.addSubview(navigationStripView)
|
self.controlsNavigationClippingView.addSubview(navigationStripView)
|
||||||
}
|
}
|
||||||
transition.setFrame(view: navigationStripView, frame: CGRect(origin: CGPoint(x: navigationStripSideInset, y: navigationStripTopInset), size: CGSize(width: availableSize.width - navigationStripSideInset * 2.0, height: 2.0)))
|
transition.setFrame(view: navigationStripView, frame: CGRect(origin: CGPoint(x: navigationStripSideInset, y: navigationStripTopInset), size: CGSize(width: availableSize.width - navigationStripSideInset * 2.0, height: navigationStripSize.height)))
|
||||||
transition.setAlpha(view: navigationStripView, alpha: self.isEditingStory ? 0.0 : 1.0)
|
transition.setAlpha(view: navigationStripView, alpha: self.isEditingStory ? 0.0 : 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let seekLabelSize = self.seekLabel.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(Text(text: "Slide left or right to seek", font: Font.semibold(14.0), color: .white)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: availableSize
|
||||||
|
)
|
||||||
|
if let seekLabelView = self.seekLabel.view {
|
||||||
|
if seekLabelView.superview == nil {
|
||||||
|
seekLabelView.alpha = 0.0
|
||||||
|
seekLabelView.isUserInteractionEnabled = false
|
||||||
|
self.controlsNavigationClippingView.addSubview(seekLabelView)
|
||||||
|
}
|
||||||
|
seekLabelView.bounds = CGRect(origin: .zero, size: seekLabelSize)
|
||||||
|
navigationStripTransition.setPosition(view: seekLabelView, position: CGPoint(x: availableSize.width / 2.0, y: navigationStripTopInset + 22.0 + 6.0 - (!isSeeking ? 12.0 : 0.0)))
|
||||||
|
navigationStripTransition.setAlpha(view: seekLabelView, alpha: isSeeking ? 1.0 : 0.0)
|
||||||
|
navigationStripTransition.setScale(view: seekLabelView, scale: isSeeking ? 1.0 : 0.02)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
component.externalState.derivedMediaSize = contentFrame.size
|
component.externalState.derivedMediaSize = contentFrame.size
|
||||||
|
Loading…
x
Reference in New Issue
Block a user