mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
266 lines
15 KiB
Swift
266 lines
15 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import Display
|
|
import AsyncDisplayKit
|
|
import SwiftSignalKit
|
|
import TelegramCore
|
|
import TelegramPresentationData
|
|
import AccountContext
|
|
import TelegramPermissions
|
|
import AppBundle
|
|
|
|
public struct PermissionControllerCustomIcon: Equatable {
|
|
let light: UIImage?
|
|
let dark: UIImage?
|
|
|
|
public init(light: UIImage?, dark: UIImage?) {
|
|
self.light = light
|
|
self.dark = dark
|
|
}
|
|
}
|
|
|
|
public enum PermissionControllerContent: Equatable {
|
|
case permission(PermissionState?)
|
|
case custom(icon: PermissionContentIcon, title: String, subtitle: String?, text: String, buttonTitle: String, secondaryButtonTitle: String?, footerText: String?)
|
|
}
|
|
|
|
private struct PermissionControllerDataState: Equatable {
|
|
var state: PermissionControllerContent?
|
|
}
|
|
|
|
private struct PermissionControllerLayoutState: Equatable {
|
|
let layout: ContainerViewLayout
|
|
let navigationHeight: CGFloat
|
|
}
|
|
|
|
private struct PermissionControllerInnerState: Equatable {
|
|
var layout: PermissionControllerLayoutState?
|
|
var data: PermissionControllerDataState
|
|
}
|
|
|
|
private struct PermissionControllerState: Equatable {
|
|
var layout: PermissionControllerLayoutState
|
|
var data: PermissionControllerDataState
|
|
}
|
|
|
|
extension PermissionControllerState {
|
|
init?(_ state: PermissionControllerInnerState) {
|
|
guard let layout = state.layout else {
|
|
return nil
|
|
}
|
|
self.init(layout: layout, data: state.data)
|
|
}
|
|
}
|
|
|
|
private func localizedString(for key: String, strings: PresentationStrings, fallback: String = "") -> String {
|
|
if let string = strings.primaryComponent.dict[key] {
|
|
return string
|
|
} else if let string = strings.secondaryComponent?.dict[key] {
|
|
return string
|
|
} else {
|
|
return fallback
|
|
}
|
|
}
|
|
|
|
final class PermissionControllerNode: ASDisplayNode {
|
|
private let context: AccountContext
|
|
private var presentationData: PresentationData
|
|
private let splitTest: PermissionUISplitTest?
|
|
|
|
private var innerState: PermissionControllerInnerState
|
|
|
|
private var contentNode: PermissionContentNode?
|
|
|
|
var allow: (() -> Void)?
|
|
var openPrivacyPolicy: (() -> Void)?
|
|
var dismiss: (() -> Void)?
|
|
|
|
init(context: AccountContext, splitTest: PermissionUISplitTest?) {
|
|
self.context = context
|
|
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
|
self.splitTest = splitTest
|
|
self.innerState = PermissionControllerInnerState(layout: nil, data: PermissionControllerDataState(state: nil))
|
|
|
|
super.init()
|
|
|
|
self.setViewBlock({
|
|
return UITracingLayerView()
|
|
})
|
|
|
|
self.updatePresentationData(self.presentationData)
|
|
}
|
|
|
|
func updatePresentationData(_ presentationData: PresentationData) {
|
|
self.presentationData = presentationData
|
|
|
|
self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
|
self.contentNode?.updatePresentationData(self.presentationData)
|
|
}
|
|
|
|
func animateIn(completion: (() -> Void)? = nil) {
|
|
self.layer.animatePosition(from: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), to: self.layer.position, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
|
|
}
|
|
|
|
func animateOut(completion: (() -> Void)? = nil) {
|
|
self.layer.animatePosition(from: self.layer.position, to: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, completion: { _ in
|
|
completion?()
|
|
})
|
|
}
|
|
|
|
public func setState(_ state: PermissionControllerContent, transition: ContainedViewLayoutTransition) {
|
|
self.updateState({ currentState -> PermissionControllerInnerState in
|
|
return PermissionControllerInnerState(layout: currentState.layout, data: PermissionControllerDataState(state: state))
|
|
}, transition: transition)
|
|
}
|
|
|
|
private func updateState(_ f: (PermissionControllerInnerState) -> PermissionControllerInnerState, transition: ContainedViewLayoutTransition) {
|
|
let updatedState = f(self.innerState)
|
|
if updatedState != self.innerState {
|
|
self.innerState = updatedState
|
|
if let state = PermissionControllerState(updatedState) {
|
|
self.transition(state: state, transition: transition)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func transition(state: PermissionControllerState, transition: ContainedViewLayoutTransition) {
|
|
let insets = state.layout.layout.insets(options: [.statusBar])
|
|
let contentFrame = CGRect(origin: CGPoint(x: 0.0, y: state.layout.navigationHeight), size: CGSize(width: state.layout.layout.size.width, height: state.layout.layout.size.height))
|
|
|
|
if let state = state.data.state {
|
|
switch state {
|
|
case let .permission(permission):
|
|
if permission?.kind.rawValue != self.contentNode?.kind {
|
|
if let dataState = permission {
|
|
let icon: UIImage?
|
|
let title: String
|
|
let text: String
|
|
let buttonTitle: String
|
|
let hasPrivacyPolicy: Bool
|
|
|
|
switch dataState {
|
|
case let .contacts(status):
|
|
icon = UIImage(bundleImageName: "Settings/Permissions/Contacts")
|
|
if let splitTest = self.splitTest, case let .modal(titleKey, textKey, allowTitleKey, allowInSettingsTitleKey) = splitTest.configuration.contacts {
|
|
title = localizedString(for: titleKey, strings: self.presentationData.strings)
|
|
text = localizedString(for: textKey, strings: self.presentationData.strings)
|
|
if status == .denied {
|
|
buttonTitle = localizedString(for: allowInSettingsTitleKey, strings: self.presentationData.strings)
|
|
} else {
|
|
buttonTitle = localizedString(for: allowTitleKey, strings: self.presentationData.strings)
|
|
}
|
|
} else {
|
|
title = self.presentationData.strings.Permissions_ContactsTitle_v0
|
|
text = self.presentationData.strings.Permissions_ContactsText_v0
|
|
if status == .denied {
|
|
buttonTitle = self.presentationData.strings.Permissions_ContactsAllowInSettings_v0
|
|
} else {
|
|
buttonTitle = self.presentationData.strings.Permissions_ContactsAllow_v0
|
|
}
|
|
}
|
|
hasPrivacyPolicy = true
|
|
case let .notifications(status):
|
|
icon = UIImage(bundleImageName: "Settings/Permissions/Notifications")
|
|
if let splitTest = self.splitTest, case let .modal(titleKey, textKey, allowTitleKey, allowInSettingsTitleKey) = splitTest.configuration.notifications {
|
|
title = localizedString(for: titleKey, strings: self.presentationData.strings, fallback: self.presentationData.strings.Permissions_NotificationsTitle_v0)
|
|
text = localizedString(for: textKey, strings: self.presentationData.strings, fallback: self.presentationData.strings.Permissions_NotificationsText_v0)
|
|
if status == .denied {
|
|
buttonTitle = localizedString(for: allowInSettingsTitleKey, strings: self.presentationData.strings, fallback: self.presentationData.strings.Permissions_NotificationsAllowInSettings_v0)
|
|
} else {
|
|
buttonTitle = localizedString(for: allowTitleKey, strings: self.presentationData.strings, fallback: self.presentationData.strings.Permissions_NotificationsAllow_v0)
|
|
}
|
|
} else {
|
|
title = self.presentationData.strings.Permissions_NotificationsTitle_v0
|
|
text = self.presentationData.strings.Permissions_NotificationsText_v0
|
|
if status == .denied {
|
|
buttonTitle = self.presentationData.strings.Permissions_NotificationsAllowInSettings_v0
|
|
} else {
|
|
buttonTitle = self.presentationData.strings.Permissions_NotificationsAllow_v0
|
|
}
|
|
}
|
|
hasPrivacyPolicy = false
|
|
case let .siri(status):
|
|
icon = UIImage(bundleImageName: "Settings/Permissions/Siri")
|
|
title = self.presentationData.strings.Permissions_SiriTitle_v0
|
|
text = self.presentationData.strings.Permissions_SiriText_v0
|
|
if status == .denied {
|
|
buttonTitle = self.presentationData.strings.Permissions_SiriAllowInSettings_v0
|
|
} else {
|
|
buttonTitle = self.presentationData.strings.Permissions_SiriAllow_v0
|
|
}
|
|
hasPrivacyPolicy = false
|
|
case .cellularData:
|
|
icon = UIImage(bundleImageName: "Settings/Permissions/CellularData")
|
|
title = self.presentationData.strings.Permissions_CellularDataTitle_v0
|
|
text = self.presentationData.strings.Permissions_CellularDataText_v0
|
|
buttonTitle = self.presentationData.strings.Permissions_CellularDataAllowInSettings_v0
|
|
hasPrivacyPolicy = false
|
|
case let .nearbyLocation(status):
|
|
icon = nil
|
|
title = self.presentationData.strings.Permissions_PeopleNearbyTitle_v0
|
|
text = self.presentationData.strings.Permissions_PeopleNearbyText_v0
|
|
if status == .denied {
|
|
buttonTitle = self.presentationData.strings.Permissions_PeopleNearbyAllowInSettings_v0
|
|
} else {
|
|
buttonTitle = self.presentationData.strings.Permissions_PeopleNearbyAllow_v0
|
|
}
|
|
hasPrivacyPolicy = false
|
|
}
|
|
|
|
let contentNode = PermissionContentNode(context: self.context, theme: self.presentationData.theme, strings: self.presentationData.strings, kind: dataState.kind.rawValue, icon: .image(icon), title: title, text: text, buttonTitle: buttonTitle, secondaryButtonTitle: nil, buttonAction: { [weak self] in
|
|
self?.allow?()
|
|
}, openPrivacyPolicy: hasPrivacyPolicy ? self.openPrivacyPolicy : nil)
|
|
self.insertSubnode(contentNode, at: 0)
|
|
contentNode.updateLayout(size: contentFrame.size, insets: insets, transition: .immediate)
|
|
contentNode.frame = contentFrame
|
|
if let currentContentNode = self.contentNode {
|
|
transition.updatePosition(node: currentContentNode, position: CGPoint(x: -contentFrame.size.width / 2.0, y: contentFrame.midY), completion: { [weak currentContentNode] _ in
|
|
currentContentNode?.removeFromSupernode()
|
|
})
|
|
transition.animateHorizontalOffsetAdditive(node: contentNode, offset: -contentFrame.width)
|
|
} else if transition.isAnimated {
|
|
contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
|
}
|
|
self.contentNode = contentNode
|
|
} else if let currentContentNode = self.contentNode {
|
|
transition.updateAlpha(node: currentContentNode, alpha: 0.0, completion: { [weak currentContentNode] _ in
|
|
currentContentNode?.removeFromSupernode()
|
|
})
|
|
self.contentNode = nil
|
|
}
|
|
} else if let contentNode = self.contentNode {
|
|
transition.updateFrame(node: contentNode, frame: contentFrame)
|
|
contentNode.updateLayout(size: contentFrame.size, insets: insets, transition: transition)
|
|
}
|
|
case let .custom(icon, title, subtitle, text, buttonTitle, secondaryButtonTitle, footerText):
|
|
if let contentNode = self.contentNode {
|
|
transition.updateFrame(node: contentNode, frame: contentFrame)
|
|
contentNode.updateLayout(size: contentFrame.size, insets: insets, transition: transition)
|
|
} else {
|
|
let contentNode = PermissionContentNode(context: self.context, theme: self.presentationData.theme, strings: self.presentationData.strings, kind: 0, icon: icon, title: title, subtitle: subtitle, text: text, buttonTitle: buttonTitle, secondaryButtonTitle: secondaryButtonTitle, footerText: footerText, buttonAction: { [weak self] in
|
|
self?.allow?()
|
|
}, openPrivacyPolicy: secondaryButtonTitle != nil ? { [weak self] in
|
|
self?.dismiss?()
|
|
} : nil)
|
|
self.insertSubnode(contentNode, at: 0)
|
|
contentNode.updateLayout(size: contentFrame.size, insets: insets, transition: .immediate)
|
|
contentNode.frame = contentFrame
|
|
self.contentNode = contentNode
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
|
self.updateState({ state in
|
|
var state = state
|
|
state.layout = PermissionControllerLayoutState(layout: layout, navigationHeight: navigationBarHeight)
|
|
return state
|
|
}, transition: transition)
|
|
}
|
|
|
|
@objc func privacyPolicyPressed() {
|
|
self.openPrivacyPolicy?()
|
|
}
|
|
}
|