diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index c50a69b53e..5a7c685b25 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -746,6 +746,7 @@ public enum PremiumIntroSource { case folders case chatsPerFolder case accounts + case about case deeplink(String?) case profile(PeerId) } diff --git a/submodules/PremiumUI/Sources/PremiumDemoScreen.swift b/submodules/PremiumUI/Sources/PremiumDemoScreen.swift index 26b2985258..26dabed1e6 100644 --- a/submodules/PremiumUI/Sources/PremiumDemoScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumDemoScreen.swift @@ -460,13 +460,15 @@ private final class DemoSheetContent: CombinedComponent { let context: AccountContext let subject: PremiumDemoScreen.Subject let source: PremiumDemoScreen.Source + let order: [PremiumPerk] let action: () -> Void let dismiss: () -> Void - init(context: AccountContext, subject: PremiumDemoScreen.Subject, source: PremiumDemoScreen.Source, action: @escaping () -> Void, dismiss: @escaping () -> Void) { + init(context: AccountContext, subject: PremiumDemoScreen.Subject, source: PremiumDemoScreen.Source, order: [PremiumPerk]?, action: @escaping () -> Void, dismiss: @escaping () -> Void) { self.context = context self.subject = subject self.source = source + self.order = order ?? [.moreUpload, .fasterDownload, .voiceToText, .noAds, .uniqueReactions, .premiumStickers, .advancedChatManagement, .profileBadge, .animatedUserpics] self.action = action self.dismiss = dismiss } @@ -481,6 +483,9 @@ private final class DemoSheetContent: CombinedComponent { if lhs.source != rhs.source { return false } + if lhs.order != rhs.order { + return false + } return true } @@ -598,164 +603,166 @@ private final class DemoSheetContent: CombinedComponent { if let reactions = state.reactions, let stickers = state.stickers { let textColor = theme.actionSheet.primaryTextColor - var items: [DemoPagerComponent.Item] = [ - DemoPagerComponent.Item( - AnyComponentWithIdentity( - id: PremiumDemoScreen.Subject.moreUpload, - component: AnyComponent( - PageComponent( - content: AnyComponent(PhoneDemoComponent( - context: component.context, - position: .bottom, - videoName: "4gb" - )), - title: strings.Premium_UploadSize, - text: strings.Premium_UploadSizeInfo, - textColor: textColor - ) - ) - ) - ), - DemoPagerComponent.Item( - AnyComponentWithIdentity( - id: PremiumDemoScreen.Subject.fasterDownload, - component: AnyComponent( - PageComponent( - content: AnyComponent(PhoneDemoComponent( - context: component.context, - position: .top, - videoName: "fastdownload" - )), - title: strings.Premium_FasterSpeed, - text: strings.Premium_FasterSpeedInfo, - textColor: textColor - ) - ) - ) - ), - DemoPagerComponent.Item( - AnyComponentWithIdentity( - id: PremiumDemoScreen.Subject.voiceToText, - component: AnyComponent( - PageComponent( - content: AnyComponent(PhoneDemoComponent( - context: component.context, - position: .top, - videoName: "voice" - )), - title: strings.Premium_VoiceToText, - text: strings.Premium_VoiceToTextInfo, - textColor: textColor - ) - ) - ) - ), - DemoPagerComponent.Item( - AnyComponentWithIdentity( - id: PremiumDemoScreen.Subject.noAds, - component: AnyComponent( - PageComponent( - content: AnyComponent(PhoneDemoComponent( - context: component.context, - position: .bottom, - videoName: "noads" - )), - title: strings.Premium_NoAds, - text: strings.Premium_NoAdsInfo, - textColor: textColor - ) - ) - ) - ), - DemoPagerComponent.Item( - AnyComponentWithIdentity( - id: PremiumDemoScreen.Subject.uniqueReactions, - component: AnyComponent( - PageComponent( - content: AnyComponent( - ReactionsCarouselComponent( - context: component.context, - theme: environment.theme, - reactions: reactions - ) - ), - title: isStandalone ? strings.Premium_ReactionsStandalone : strings.Premium_Reactions, - text: isStandalone ? strings.Premium_ReactionsStandaloneInfo : strings.Premium_ReactionsInfo, - textColor: textColor - ) - ) - ) - ), - DemoPagerComponent.Item( - AnyComponentWithIdentity( - id: PremiumDemoScreen.Subject.premiumStickers, - component: AnyComponent( - PageComponent( - content: AnyComponent( - StickersCarouselComponent( - context: component.context, - stickers: stickers - ) - ), - title: strings.Premium_Stickers, - text: strings.Premium_StickersInfo, - textColor: textColor - ) - ) - ) - ), - DemoPagerComponent.Item( - AnyComponentWithIdentity( - id: PremiumDemoScreen.Subject.advancedChatManagement, - component: AnyComponent( - PageComponent( - content: AnyComponent(PhoneDemoComponent( - context: component.context, - position: .top, - videoName: "fastdownload" - )), - title: strings.Premium_ChatManagement, - text: strings.Premium_ChatManagementInfo, - textColor: textColor - ) - ) - ) - ), - DemoPagerComponent.Item( - AnyComponentWithIdentity( - id: PremiumDemoScreen.Subject.profileBadge, - component: AnyComponent( - PageComponent( - content: AnyComponent(PhoneDemoComponent( - context: component.context, - position: .top, - videoName: "badge" - )), - title: strings.Premium_Badge, - text: strings.Premium_BadgeInfo, - textColor: textColor - ) - ) - ) - ), - DemoPagerComponent.Item( - AnyComponentWithIdentity( - id: PremiumDemoScreen.Subject.animatedUserpics, - component: AnyComponent( - PageComponent( - content: AnyComponent(PhoneDemoComponent( - context: component.context, - position: .top, - videoName: "badge" - )), - title: strings.Premium_Avatar, - text: strings.Premium_AvatarInfo, - textColor: textColor - ) + var availableItems: [PremiumPerk: DemoPagerComponent.Item] = [:] + + availableItems[.moreUpload] = DemoPagerComponent.Item( + AnyComponentWithIdentity( + id: PremiumDemoScreen.Subject.moreUpload, + component: AnyComponent( + PageComponent( + content: AnyComponent(PhoneDemoComponent( + context: component.context, + position: .bottom, + videoName: "4gb" + )), + title: strings.Premium_UploadSize, + text: strings.Premium_UploadSizeInfo, + textColor: textColor ) ) ) - ] + ) + availableItems[.fasterDownload] = DemoPagerComponent.Item( + AnyComponentWithIdentity( + id: PremiumDemoScreen.Subject.fasterDownload, + component: AnyComponent( + PageComponent( + content: AnyComponent(PhoneDemoComponent( + context: component.context, + position: .top, + videoName: "fastdownload" + )), + title: strings.Premium_FasterSpeed, + text: strings.Premium_FasterSpeedInfo, + textColor: textColor + ) + ) + ) + ) + availableItems[.voiceToText] = DemoPagerComponent.Item( + AnyComponentWithIdentity( + id: PremiumDemoScreen.Subject.voiceToText, + component: AnyComponent( + PageComponent( + content: AnyComponent(PhoneDemoComponent( + context: component.context, + position: .top, + videoName: "voice" + )), + title: strings.Premium_VoiceToText, + text: strings.Premium_VoiceToTextInfo, + textColor: textColor + ) + ) + ) + ) + availableItems[.noAds] = DemoPagerComponent.Item( + AnyComponentWithIdentity( + id: PremiumDemoScreen.Subject.noAds, + component: AnyComponent( + PageComponent( + content: AnyComponent(PhoneDemoComponent( + context: component.context, + position: .bottom, + videoName: "noads" + )), + title: strings.Premium_NoAds, + text: strings.Premium_NoAdsInfo, + textColor: textColor + ) + ) + ) + ) + availableItems[.uniqueReactions] = DemoPagerComponent.Item( + AnyComponentWithIdentity( + id: PremiumDemoScreen.Subject.uniqueReactions, + component: AnyComponent( + PageComponent( + content: AnyComponent( + ReactionsCarouselComponent( + context: component.context, + theme: environment.theme, + reactions: reactions + ) + ), + title: isStandalone ? strings.Premium_ReactionsStandalone : strings.Premium_Reactions, + text: isStandalone ? strings.Premium_ReactionsStandaloneInfo : strings.Premium_ReactionsInfo, + textColor: textColor + ) + ) + ) + ) + availableItems[.premiumStickers] = DemoPagerComponent.Item( + AnyComponentWithIdentity( + id: PremiumDemoScreen.Subject.premiumStickers, + component: AnyComponent( + PageComponent( + content: AnyComponent( + StickersCarouselComponent( + context: component.context, + stickers: stickers + ) + ), + title: strings.Premium_Stickers, + text: strings.Premium_StickersInfo, + textColor: textColor + ) + ) + ) + ) + availableItems[.advancedChatManagement] = DemoPagerComponent.Item( + AnyComponentWithIdentity( + id: PremiumDemoScreen.Subject.advancedChatManagement, + component: AnyComponent( + PageComponent( + content: AnyComponent(PhoneDemoComponent( + context: component.context, + position: .top, + videoName: "fastdownload" + )), + title: strings.Premium_ChatManagement, + text: strings.Premium_ChatManagementInfo, + textColor: textColor + ) + ) + ) + ) + availableItems[.profileBadge] = DemoPagerComponent.Item( + AnyComponentWithIdentity( + id: PremiumDemoScreen.Subject.profileBadge, + component: AnyComponent( + PageComponent( + content: AnyComponent(PhoneDemoComponent( + context: component.context, + position: .top, + videoName: "badge" + )), + title: strings.Premium_Badge, + text: strings.Premium_BadgeInfo, + textColor: textColor + ) + ) + ) + ) + availableItems[.animatedUserpics] = DemoPagerComponent.Item( + AnyComponentWithIdentity( + id: PremiumDemoScreen.Subject.animatedUserpics, + component: AnyComponent( + PageComponent( + content: AnyComponent(PhoneDemoComponent( + context: component.context, + position: .top, + videoName: "badge" + )), + title: strings.Premium_Avatar, + text: strings.Premium_AvatarInfo, + textColor: textColor + ) + ) + ) + ) + + var items: [DemoPagerComponent.Item] = component.order.compactMap { availableItems[$0] } let index: Int switch component.source { case .intro: @@ -881,12 +888,14 @@ private final class DemoSheetComponent: CombinedComponent { let context: AccountContext let subject: PremiumDemoScreen.Subject let source: PremiumDemoScreen.Source + let order: [PremiumPerk]? let action: () -> Void - init(context: AccountContext, subject: PremiumDemoScreen.Subject, source: PremiumDemoScreen.Source, action: @escaping () -> Void) { + init(context: AccountContext, subject: PremiumDemoScreen.Subject, source: PremiumDemoScreen.Source, order: [PremiumPerk]?, action: @escaping () -> Void) { self.context = context self.subject = subject self.source = source + self.order = order self.action = action } @@ -900,6 +909,9 @@ private final class DemoSheetComponent: CombinedComponent { if lhs.source != rhs.source { return false } + if lhs.order != rhs.order { + return false + } return true } @@ -919,6 +931,7 @@ private final class DemoSheetComponent: CombinedComponent { context: context.component.context, subject: context.component.subject, source: context.component.source, + order: context.component.order, action: context.component.action, dismiss: { animateOut.invoke(Action { _ in @@ -989,8 +1002,12 @@ public class PremiumDemoScreen: ViewControllerComponentContainer { return self._ready } - public init(context: AccountContext, subject: PremiumDemoScreen.Subject, source: PremiumDemoScreen.Source = .other, action: @escaping () -> Void) { - super.init(context: context, component: DemoSheetComponent(context: context, subject: subject, source: source, action: action), navigationBarAppearance: .none) + public convenience init(context: AccountContext, subject: PremiumDemoScreen.Subject, source: PremiumDemoScreen.Source = .other, action: @escaping () -> Void) { + self.init(context: context, subject: subject, source: source, order: nil, action: action) + } + + init(context: AccountContext, subject: PremiumDemoScreen.Subject, source: PremiumDemoScreen.Source = .other, order: [PremiumPerk]?, action: @escaping () -> Void) { + super.init(context: context, component: DemoSheetComponent(context: context, subject: subject, source: source, order: order, action: action), navigationBarAppearance: .none) self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait) diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index 1480249c39..81bd4a84d4 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -34,6 +34,7 @@ public enum PremiumSource: Equatable { case folders case chatsPerFolder case accounts + case about case deeplink(String?) case profile(PeerId) @@ -65,6 +66,8 @@ public enum PremiumSource: Equatable { return "double_limits__dialog_filters_chats" case .accounts: return "double_limits__accounts" + case .about: + return "double_limits__about" case let .profile(id): return "profile__\(id.id._internalGetInt64Value())" case let .deeplink(reference): @@ -77,7 +80,7 @@ public enum PremiumSource: Equatable { } } -private enum PremiumPerk: CaseIterable { +enum PremiumPerk: CaseIterable { case doubleLimits case moreUpload case fasterDownload @@ -990,6 +993,7 @@ private final class PremiumIntroScreenContentComponent: CombinedComponent { context: accountContext, subject: demoSubject, source: .intro(state?.price), + order: state?.configuration.perks, action: { dismissImpl?() if !isPremium { diff --git a/submodules/TelegramCore/Sources/Account/Account.swift b/submodules/TelegramCore/Sources/Account/Account.swift index ccb31262bc..7834869378 100644 --- a/submodules/TelegramCore/Sources/Account/Account.swift +++ b/submodules/TelegramCore/Sources/Account/Account.swift @@ -1150,6 +1150,7 @@ public class Account { self.managedOperationsDisposable.add(managedConfigurationUpdates(accountManager: accountManager, postbox: self.postbox, network: self.network).start()) self.managedOperationsDisposable.add(managedVoipConfigurationUpdates(postbox: self.postbox, network: self.network).start()) self.managedOperationsDisposable.add(managedAppConfigurationUpdates(postbox: self.postbox, network: self.network).start()) + self.managedOperationsDisposable.add(managedPremiumPromoConfigurationUpdates(postbox: self.postbox, network: self.network).start()) self.managedOperationsDisposable.add(managedAutodownloadSettingsUpdates(accountManager: accountManager, network: self.network).start()) self.managedOperationsDisposable.add(managedTermsOfServiceUpdates(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start()) self.managedOperationsDisposable.add(managedAppUpdateInfo(network: self.network, stateManager: self.stateManager).start()) diff --git a/submodules/TelegramCore/Sources/State/ManagedPremiumPromoConfigurationUpdates.swift b/submodules/TelegramCore/Sources/State/ManagedPremiumPromoConfigurationUpdates.swift new file mode 100644 index 0000000000..b328b464b8 --- /dev/null +++ b/submodules/TelegramCore/Sources/State/ManagedPremiumPromoConfigurationUpdates.swift @@ -0,0 +1,67 @@ +import Foundation +import Postbox +import SwiftSignalKit +import TelegramApi +import MtProtoKit + + +func updatePremiumPromoConfigurationOnce(postbox: Postbox, network: Network) -> Signal { + return network.request(Api.functions.help.getPremiumPromo()) + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { result -> Signal in + guard let result = result else { + return .complete() + } + return postbox.transaction { transaction -> Void in + updatePremiumPromoConfiguration(transaction: transaction, { configuration -> PremiumPromoConfiguration in + return PremiumPromoConfiguration(apiPremiumPromo: result) + }) + } + } +} + +func managedPremiumPromoConfigurationUpdates(postbox: Postbox, network: Network) -> Signal { + let poll = Signal { subscriber in + return updatePremiumPromoConfigurationOnce(postbox: postbox, network: network).start(completed: { + subscriber.putCompletion() + }) + } + return (poll |> then(.complete() |> suspendAwareDelay(10.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart +} + +private func currentPremiumPromoConfiguration(transaction: Transaction) -> PremiumPromoConfiguration { + if let entry = transaction.getPreferencesEntry(key: PreferencesKeys.premiumPromo)?.get(PremiumPromoConfiguration.self) { + return entry + } else { + return PremiumPromoConfiguration.defaultValue + } +} + +private func updatePremiumPromoConfiguration(transaction: Transaction, _ f: (PremiumPromoConfiguration) -> PremiumPromoConfiguration) { + let current = currentPremiumPromoConfiguration(transaction: transaction) + let updated = f(current) + if updated != current { + transaction.setPreferencesEntry(key: PreferencesKeys.premiumPromo, value: PreferencesEntry(updated)) + } +} + +private extension PremiumPromoConfiguration { + init(apiPremiumPromo: Api.help.PremiumPromo) { + switch apiPremiumPromo { + case let .premiumPromo(statusText, statusEntities, videoSections, videoFiles): + self.status = statusText + self.statusEntities = messageTextEntitiesFromApiEntities(statusEntities) + + var videos: [String: TelegramMediaFile] = [:] + for (key, document) in zip(videoSections, videoFiles) { + if let file = telegramMediaFileFromApiDocument(document) { + videos[key] = file + } + } + self.videos = videos + } + } +} diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift index eecd7f3430..bf3ea7c899 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_Namespaces.swift @@ -232,6 +232,7 @@ private enum PreferencesKeyValues: Int32 { case chatListFiltersFeaturedState = 22 case secretChatSettings = 23 case reactionSettings = 24 + case premiumPromo = 25 } public func applicationSpecificPreferencesKey(_ value: Int32) -> ValueBoxKey { @@ -360,6 +361,12 @@ public struct PreferencesKeys { key.setInt32(0, value: PreferencesKeyValues.reactionSettings.rawValue) return key }() + + public static let premiumPromo: ValueBoxKey = { + let key = ValueBoxKey(length: 4) + key.setInt32(0, value: PreferencesKeyValues.premiumPromo.rawValue) + return key + }() } private enum SharedDataKeyValues: Int32 { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_PremiumPromoConfiguration.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_PremiumPromoConfiguration.swift new file mode 100644 index 0000000000..35bcf7a304 --- /dev/null +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_PremiumPromoConfiguration.swift @@ -0,0 +1,75 @@ +import Foundation +import Postbox + +public struct PremiumPromoConfiguration: Codable, Equatable { + enum CodingKeys: String, CodingKey { + case status + case statusEntities + case videos + } + + private struct DictionaryPair: Codable { + var key: String + var value: TelegramMediaFile + + init(_ key: String, value: TelegramMediaFile) { + self.key = key + self.value = value + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: StringCodingKey.self) + + self.key = try container.decode(String.self, forKey: "k") + self.value = try container.decode(TelegramMediaFile.self, forKey: "v") + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: StringCodingKey.self) + + try container.encode(self.key, forKey: "k") + try container.encode(self.value, forKey: "v") + } + } + + public var status: String + public var statusEntities: [MessageTextEntity] + public var videos: [String: TelegramMediaFile] + + public static var defaultValue: PremiumPromoConfiguration { + return PremiumPromoConfiguration(status: "", statusEntities: [], videos: [:]) + } + + init(status: String, statusEntities: [MessageTextEntity], videos: [String: TelegramMediaFile]) { + self.status = status + self.statusEntities = statusEntities + self.videos = videos + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + self.status = try container.decode(String.self, forKey: .status) + self.statusEntities = try container.decode([MessageTextEntity].self, forKey: .statusEntities) + + var videos: [String: TelegramMediaFile] = [:] + let pairs = try container.decode([DictionaryPair].self, forKey: .videos) + for pair in pairs { + videos[pair.key] = pair.value + } + self.videos = videos + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + try container.encode(self.status, forKey: .status) + try container.encode(self.statusEntities, forKey: .statusEntities) + + var pairs: [DictionaryPair] = [] + for (key, file) in self.videos { + pairs.append(DictionaryPair(key, value: file)) + } + try container.encode(pairs, forKey: .videos) + } +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Data/ConfigurationData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Data/ConfigurationData.swift index a37d93e54f..447f8e3107 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Data/ConfigurationData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Data/ConfigurationData.swift @@ -354,5 +354,26 @@ public extension TelegramEngine.EngineData.Item { return localizationListState } } + + public struct PremiumPromo: TelegramEngineDataItem, PostboxViewDataItem { + public typealias Result = PremiumPromoConfiguration + + public init() { + } + + var key: PostboxViewKey { + return .preferences(keys: Set([PreferencesKeys.premiumPromo])) + } + + func extract(view: PostboxView) -> Result { + guard let view = view as? PreferencesView else { + preconditionFailure() + } + guard let premiumPromoConfiguration = view.values[PreferencesKeys.premiumPromo]?.get(PremiumPromoConfiguration.self) else { + return PremiumPromoConfiguration.defaultValue + } + return premiumPromoConfiguration + } + } } } diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 48c0ab5a7f..a621406044 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -1491,6 +1491,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { mappedSource = .chatsPerFolder case .accounts: mappedSource = .accounts + case .about: + mappedSource = .about case let .deeplink(reference): mappedSource = .deeplink(reference) case let .profile(peerId): diff --git a/submodules/TelegramUI/Sources/StickerPaneTrendingListGridItem.swift b/submodules/TelegramUI/Sources/StickerPaneTrendingListGridItem.swift index e04c438dba..efd64396c7 100644 --- a/submodules/TelegramUI/Sources/StickerPaneTrendingListGridItem.swift +++ b/submodules/TelegramUI/Sources/StickerPaneTrendingListGridItem.swift @@ -526,8 +526,8 @@ class StickerPaneTrendingListGridItemNode: GridItemNode { self.dismissButtonNode.setImage(PresentationResourcesChat.chatInputMediaPanelGridDismissImage(item.theme), for: []) } - let leftInset: CGFloat = 12.0 - let rightInset: CGFloat = 16.0 + let leftInset: CGFloat = 9.0 + let rightInset: CGFloat = 18.0 + UIScreenPixel let topOffset: CGFloat = 9.0 let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.strings.StickerPacksSettings_FeaturedPacks.uppercased(), font: titleFont, textColor: item.theme.chat.inputMediaPanel.stickersSectionTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - leftInset - rightInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))