Various improvements

This commit is contained in:
Ilya Laktyushin 2024-11-12 03:16:44 +04:00
parent 3ed05bc39d
commit 3fd2bf7f2f
7 changed files with 147 additions and 72 deletions

View File

@ -506,6 +506,8 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate {
let containerFrame: CGRect
let clipFrame: CGRect
let containerScale: CGFloat
let isFullscreen = controllers.last?.isFullscreen == true
if case .compact = layout.metrics.widthClass {
self.clipNode.clipsToBounds = true
@ -524,7 +526,7 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate {
}
var containerTopInset: CGFloat
if isLandscape || controllers.last?.isFullscreen == true {
if isLandscape || isFullscreen {
containerTopInset = 0.0
containerLayout = layout
@ -560,7 +562,7 @@ final class AttachmentContainer: ASDisplayNode, ASGestureRecognizerDelegate {
clipFrame = CGRect(x: containerFrame.minX + overflowInset, y: containerFrame.minY, width: containerFrame.width - overflowInset * 2.0, height: containerFrame.height)
}
} else {
containerLayout = ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: layout.intrinsicInsets.bottom, right: 0.0), safeInsets: UIEdgeInsets(), additionalInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: layout.inVoiceOver)
containerLayout = ContainerViewLayout(size: layout.size, metrics: layout.metrics, deviceMetrics: layout.deviceMetrics, intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: layout.intrinsicInsets.bottom, right: 0.0), safeInsets: UIEdgeInsets(), additionalInsets: UIEdgeInsets(), statusBarHeight: isFullscreen ? layout.statusBarHeight : nil, inputHeight: isFullscreen ? layout.inputHeight : nil, inputHeightIsInteractivellyChanging: false, inVoiceOver: layout.inVoiceOver)
let unscaledFrame = CGRect(origin: CGPoint(), size: containerLayout.size)
containerScale = 1.0

View File

@ -1057,50 +1057,57 @@ public class AttachmentController: ViewController, MinimizableController {
var containerLayout = layout
let containerRect: CGRect
if case .regular = layout.metrics.widthClass {
let availableHeight = layout.size.height - (layout.inputHeight ?? 0.0) - 60.0
let size = CGSize(width: 390.0, height: min(620.0, availableHeight))
let insets = layout.insets(options: [.input])
let masterWidth = min(max(320.0, floor(layout.size.width / 3.0)), floor(layout.size.width / 2.0))
let position: CGPoint
let positionY = layout.size.height - size.height - insets.bottom - 40.0
if let sourceRect = controller.getSourceRect?() {
position = CGPoint(x: min(layout.size.width - size.width - 28.0, floor(sourceRect.midX - size.width / 2.0)), y: min(positionY, sourceRect.minY - size.height))
if controller.isFullscreen {
containerRect = CGRect(origin: .zero, size: layout.size)
self.wrapperNode.cornerRadius = 0.0
self.wrapperNode.view.mask = nil
self.shadowNode.alpha = 0.0
} else {
position = CGPoint(x: masterWidth - 174.0, y: positionY)
}
if controller.isStandalone && !controller.forceSourceRect {
var containerY = floorToScreenPixels((layout.size.height - size.height) / 2.0)
if let inputHeight = layout.inputHeight, inputHeight > 88.0 {
containerY = layout.size.height - inputHeight - size.height - 80.0
let availableHeight = layout.size.height - (layout.inputHeight ?? 0.0) - 60.0
let size = CGSize(width: 390.0, height: min(620.0, availableHeight))
let insets = layout.insets(options: [.input])
let masterWidth = min(max(320.0, floor(layout.size.width / 3.0)), floor(layout.size.width / 2.0))
let position: CGPoint
let positionY = layout.size.height - size.height - insets.bottom - 40.0
if let sourceRect = controller.getSourceRect?() {
position = CGPoint(x: min(layout.size.width - size.width - 28.0, floor(sourceRect.midX - size.width / 2.0)), y: min(positionY, sourceRect.minY - size.height))
} else {
position = CGPoint(x: masterWidth - 174.0, y: positionY)
}
if controller.isStandalone && !controller.forceSourceRect {
var containerY = floorToScreenPixels((layout.size.height - size.height) / 2.0)
if let inputHeight = layout.inputHeight, inputHeight > 88.0 {
containerY = layout.size.height - inputHeight - size.height - 80.0
}
containerRect = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - size.width) / 2.0), y: containerY), size: size)
} else {
containerRect = CGRect(origin: position, size: size)
}
containerLayout.size = containerRect.size
containerLayout.intrinsicInsets.bottom = 12.0
containerLayout.inputHeight = nil
if controller.isStandalone {
self.wrapperNode.cornerRadius = 10.0
} else if self.wrapperNode.view.mask == nil {
let maskView = UIImageView()
maskView.image = generateMaskImage()
maskView.contentMode = .scaleToFill
self.wrapperNode.view.mask = maskView
}
if let maskView = self.wrapperNode.view.mask {
transition.updateFrame(view: maskView, frame: CGRect(origin: CGPoint(), size: size))
}
self.shadowNode.alpha = 1.0
if self.shadowNode.image == nil {
self.shadowNode.image = generateShadowImage()
}
containerRect = CGRect(origin: CGPoint(x: floorToScreenPixels((layout.size.width - size.width) / 2.0), y: containerY), size: size)
} else {
containerRect = CGRect(origin: position, size: size)
}
containerLayout.size = containerRect.size
containerLayout.intrinsicInsets.bottom = 12.0
containerLayout.inputHeight = nil
if controller.isStandalone {
self.wrapperNode.cornerRadius = 10.0
} else if self.wrapperNode.view.mask == nil {
let maskView = UIImageView()
maskView.image = generateMaskImage()
maskView.contentMode = .scaleToFill
self.wrapperNode.view.mask = maskView
}
if let maskView = self.wrapperNode.view.mask {
transition.updateFrame(view: maskView, frame: CGRect(origin: CGPoint(), size: size))
}
self.shadowNode.alpha = 1.0
if self.shadowNode.image == nil {
self.shadowNode.image = generateShadowImage()
}
} else {
let containerHeight: CGFloat

View File

@ -167,6 +167,7 @@ final class ChatListNoticeItemNode: ItemListRevealOptionsItemNode {
let sideInset: CGFloat = params.leftInset + 16.0
let rightInset: CGFloat = sideInset + 24.0
var titleRightInset = rightInset
let verticalInset: CGFloat = 9.0
var spacing: CGFloat = 0.0
@ -214,6 +215,7 @@ final class ChatListNoticeItemNode: ItemListRevealOptionsItemNode {
titleString = titleStringValue
textString = NSAttributedString(string: item.strings.ChatList_PremiumAnnualDiscountText, font: textFont, textColor: item.theme.rootController.navigationBar.secondaryTextColor)
titleRightInset = sideInset
case let .premiumRestore(discount):
let discountString = "\(discount)%"
let rawTitleString = item.strings.ChatList_PremiumRestoreDiscountTitle(discountString)
@ -291,7 +293,7 @@ final class ChatListNoticeItemNode: ItemListRevealOptionsItemNode {
leftInset += avatarsWidth + 4.0
}
let titleLayout = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset, height: 100.0), alignment: alignment, lineSpacing: 0.18))
let titleLayout = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - titleRightInset, height: 100.0), alignment: alignment, lineSpacing: 0.18))
let textLayout = makeTextLayout(TextNodeLayoutArguments(attributedString: textString, maximumNumberOfLines: 10, truncationType: .end, constrainedSize: CGSize(width: params.width - leftInset - rightInset, height: 100.0), alignment: alignment, lineSpacing: 0.18))

View File

@ -1545,23 +1545,33 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
if let _ = user.botInfo {
//TODO:localize
items[.permissions]!.append(PeerInfoScreenHeaderItem(id: 30, text: "ALLOW ACCESS TO"))
var canManageEmojiStatus = false
if let cachedData = data.cachedData as? CachedUserData, cachedData.flags.contains(.botCanManageEmojiStatus) {
canManageEmojiStatus = true
}
items[.permissions]!.append(PeerInfoScreenSwitchItem(id: 31, text: "Emoji Status", value: canManageEmojiStatus, icon: UIImage(bundleImageName: "Chat/Info/Status"), isLocked: false, toggled: { value in
let _ = (context.engine.peers.toggleBotEmojiStatusAccess(peerId: user.id, enabled: value)
|> deliverOnMainQueue).startStandalone()
}))
if canManageEmojiStatus || data.webAppPermissions?.emojiStatus?.isRequested == true {
items[.permissions]!.append(PeerInfoScreenSwitchItem(id: 31, text: "Emoji Status", value: canManageEmojiStatus, icon: UIImage(bundleImageName: "Chat/Info/Status"), isLocked: false, toggled: { value in
let _ = (context.engine.peers.toggleBotEmojiStatusAccess(peerId: user.id, enabled: value)
|> deliverOnMainQueue).startStandalone()
let _ = updateWebAppPermissionsStateInteractively(context: context, peerId: user.id) { current in
return WebAppPermissionsState(location: current?.location, emojiStatus: WebAppPermissionsState.EmojiStatus(isRequested: true))
}.startStandalone()
}))
}
if data.webAppPermissions?.location?.isRequested == true || data.webAppPermissions?.location?.isAllowed == true {
items[.permissions]!.append(PeerInfoScreenSwitchItem(id: 32, text: "Geolocation", value: data.webAppPermissions?.location?.isAllowed ?? false, icon: UIImage(bundleImageName: "Chat/Info/Location"), isLocked: false, toggled: { value in
let _ = updateWebAppPermissionsStateInteractively(context: context, peerId: user.id) { current in
return WebAppPermissionsState(location: WebAppPermissionsState.Location(isRequested: true, isAllowed: value))
return WebAppPermissionsState(location: WebAppPermissionsState.Location(isRequested: true, isAllowed: value), emojiStatus: current?.emojiStatus)
}.startStandalone()
}))
}
if !items[.permissions]!.isEmpty {
items[.permissions]!.insert(PeerInfoScreenHeaderItem(id: 30, text: "ALLOW ACCESS TO"), at: 0)
}
}
if let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) {

View File

@ -222,7 +222,7 @@ func openWebAppImpl(
} else {
source = url.isEmpty ? .generic : .simple
}
let params = WebAppParameters(source: source, peerId: chatPeer?.id ?? botId, botId: botId, botName: botName, botVerified: botVerified, botAddress: botPeer.addressName ?? "", appName: nil, url: result.url, queryId: nil, payload: payload, buttonText: buttonText, keepAliveSignal: nil, forceHasSettings: false, fullSize: result.flags.contains(.fullSize), isFullscreen: result.flags.contains(.fullScreen), appSettings: appSettings)
let params = WebAppParameters(source: source, peerId: chatPeer?.id ?? botId, botId: botId, botName: botName, botVerified: botVerified, botAddress: botPeer.addressName ?? "", appName: "", url: result.url, queryId: nil, payload: payload, buttonText: buttonText, keepAliveSignal: nil, forceHasSettings: false, fullSize: result.flags.contains(.fullSize), isFullscreen: result.flags.contains(.fullScreen), appSettings: appSettings)
let controller = standaloneWebAppController(context: context, updatedPresentationData: updatedPresentationData, params: params, threadId: threadId, openUrl: { [weak parentController] url, concealed, forceUpdate, commit in
ChatControllerImpl.botOpenUrl(context: context, peerId: chatPeer?.id ?? botId, controller: parentController as? ChatControllerImpl, url: url, concealed: concealed, forceUpdate: forceUpdate, present: { c, a in
presentImpl?(c, a)

View File

@ -1423,7 +1423,11 @@ public final class WebAppController: ViewController, AttachmentContainable {
case "web_app_check_location":
self.checkLocation()
case "web_app_open_location_settings":
break
if let lastTouchTimestamp = self.webView?.lastTouchTimestamp, currentTimestamp < lastTouchTimestamp + 10.0 {
self.webView?.lastTouchTimestamp = nil
self.openLocationSettings()
}
case "web_app_send_prepared_message":
if let json = json, let id = json["id"] as? String {
self.sendPreparedMessage(id: id)
@ -2409,10 +2413,9 @@ public final class WebAppController: ViewController, AttachmentContainable {
guard let self, let controller = self.controller else {
return
}
let context = self.context
let botId = controller.botId
if result {
let context = self.context
let botId = controller.botId
if !context.isPremium {
var replaceImpl: ((ViewController) -> Void)?
let demoController = context.sharedContext.makePremiumDemoController(context: context, subject: .emojiStatus, forceDark: false, action: {
@ -2431,7 +2434,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
|> deliverOnMainQueue).startStandalone(completed: { [weak self] in
self?.webView?.sendEvent(name: "emoji_status_access_requested", data: "{status: \"allowed\"}")
})
//TODO:localize
if let botPeer {
let resultController = UndoOverlayController(
@ -2451,6 +2454,10 @@ public final class WebAppController: ViewController, AttachmentContainable {
} else {
self.webView?.sendEvent(name: "emoji_status_access_requested", data: "{status: \"cancelled\"}")
}
let _ = updateWebAppPermissionsStateInteractively(context: context, peerId: botId) { current in
return WebAppPermissionsState(location: current?.location, emojiStatus: WebAppPermissionsState.EmojiStatus(isRequested: true))
}.startStandalone()
}
)
alertController.dismissed = { [weak self] byOutsideTap in
@ -2550,6 +2557,21 @@ public final class WebAppController: ViewController, AttachmentContainable {
})
}
fileprivate func openLocationSettings() {
guard let controller = self.controller else {
return
}
let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: controller.botId))
|> deliverOnMainQueue).start(next: { [weak self] peer in
guard let self, let controller = self.controller, let peer else {
return
}
if let infoController = self.context.sharedContext.makePeerInfoController(context: self.context, updatedPresentationData: nil, peer: peer._asPeer(), mode: .generic, avatarInitiallyExpanded: false, fromChat: false, requestsContext: nil) {
controller.parentController()?.push(infoController)
}
})
}
fileprivate func checkLocation() {
guard let controller = self.controller else {
return
@ -2671,7 +2693,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
self.webView?.sendEvent(name: "location_requested", data: JSON(dictionary: data)?.string)
}
let _ = updateWebAppPermissionsStateInteractively(context: context, peerId: botId) { current in
return WebAppPermissionsState(location: WebAppPermissionsState.Location(isRequested: true, isAllowed: result))
return WebAppPermissionsState(location: WebAppPermissionsState.Location(isRequested: true, isAllowed: result), emojiStatus: current?.emojiStatus)
}.start()
}
)
@ -2960,6 +2982,17 @@ public final class WebAppController: ViewController, AttachmentContainable {
self?.controllerNode.webView?.reload()
})))
//TODO:localize
if let _ = self?.appName {
items.append(.action(ContextMenuActionItem(text: "Add to Home Screen", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddCircle"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] c, _ in
c?.dismiss(completion: nil)
self?.controllerNode.addToHomeScreen()
})))
}
items.append(.action(ContextMenuActionItem(text: presentationData.strings.WebApp_TermsOfUse, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Info"), color: theme.contextMenu.primaryColor)
@ -3002,18 +3035,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
self.context.sharedContext.openExternalUrl(context: self.context, urlContext: .generic, url: self.presentationData.strings.WebApp_PrivacyPolicy_URL, forceExternal: false, presentationData: self.presentationData, navigationController: self.getNavigationController(), dismissInput: {})
}
})))
#if DEBUG
//TODO:localize
items.append(.action(ContextMenuActionItem(text: "Add to Home Screen", icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddCircle"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] c, _ in
c?.dismiss(completion: nil)
self?.controllerNode.addToHomeScreen()
})))
#endif
if let _ = attachMenuBot, [.attachMenu, .settings, .generic].contains(source) {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.WebApp_RemoveBot, textColor: .destructive, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor)

View File

@ -8,6 +8,7 @@ import TelegramUIPreferences
public struct WebAppPermissionsState: Codable {
enum CodingKeys: String, CodingKey {
case location
case emojiStatus
}
public struct Location: Codable {
@ -41,25 +42,56 @@ public struct WebAppPermissionsState: Codable {
try container.encode(self.isAllowed, forKey: .isAllowed)
}
}
public struct EmojiStatus: Codable {
enum CodingKeys: String, CodingKey {
case isRequested
}
public let isRequested: Bool
public init(
isRequested: Bool
) {
self.isRequested = isRequested
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.isRequested = try container.decode(Bool.self, forKey: .isRequested)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.isRequested, forKey: .isRequested)
}
}
public let location: Location?
public let emojiStatus: EmojiStatus?
public init(
location: Location?
location: Location?,
emojiStatus: EmojiStatus?
) {
self.location = location
self.emojiStatus = emojiStatus
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.location = try container.decode(WebAppPermissionsState.Location.self, forKey: .location)
self.location = try container.decodeIfPresent(WebAppPermissionsState.Location.self, forKey: .location)
self.emojiStatus = try container.decodeIfPresent(WebAppPermissionsState.EmojiStatus.self, forKey: .emojiStatus)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(self.location, forKey: .location)
try container.encodeIfPresent(self.emojiStatus, forKey: .emojiStatus)
}
}