Various improvements

This commit is contained in:
Ilya Laktyushin 2024-11-15 00:48:41 +04:00
parent 40080bbe1f
commit 490d0f8ea7
13 changed files with 289 additions and 170 deletions

View File

@ -7525,9 +7525,6 @@ Sorry for the inconvenience.";
"WebApp.OpenWebViewAlertTitle" = "Open Web App";
"WebApp.OpenWebViewAlertText" = "**%@** would like to open its web app to proceed.\n\nIt will be able to access your **IP address** and basic device info.";
"WebApp.MessagePreview" = "Message Preview";
"WebApp.Send" = "Send";
"WebApp.RemoveConfirmationTitle" = "Remove Bot";
"WebApp.RemoveConfirmationText" = "Remove **%@** from the attachment menu?";
@ -13242,3 +13239,50 @@ Sorry for the inconvenience.";
"PrivacySettings.ValueBotsPlus" = "Mini Apps +%@";
"PrivacySettings.CategoryBots" = "Mini Apps";
"PeerInfo.Permissions.Title" = "ALLOW ACCESS TO";
"PeerInfo.Permissions.EmojiStatus" = "Emoji Status";
"PeerInfo.Permissions.Geolocation" = "Geolocation";
"PeerInfo.Permissions.Biometry" = "Biometry";
"Stars.Transaction.Subscription" = "Subscription";
"Stars.Transaction.Subscription.Bot" = "Bot";
"Stars.Transaction.Subscription.Business" = "Business";
"Stars.Transfer.BotSubscribeInfo" = "Do you want to subscribe to **%1$@** in **%2$@** for **%3$@** per month?";
"Stars.Transfer.BotSubscribeInfo.Stars_1" = "%@ Star";
"Stars.Transfer.BotSubscribeInfo.Stars_any" = "%@ Stars";
"Stars.Transfer.SubscribeFor" = "Subscribe for";
"Stars.Transfer.SubscribePerMonth" = "/ month";
"WebApp.AddToHomeScreen" = "Add to Home Screen";
"WebApp.Emoji.Title" = "Set Emoji Status";
"WebApp.Emoji.Text" = "Do you want to set this emoji status suggested by **%@**?";
"WebApp.Emoji.DurationText" = "Do you want to set this emoji status suggested by **%1$@** for **%2$@**?";
"WebApp.Emoji.Confirm" = "Confirm";
"WebApp.Emoji.Succeed" = "Your emoji status updated.";
"WebApp.Emoji.DurationSucceed" = "Your emoji status set for %@.";
"WebApp.EmojiPermission.Text" = "**%1$@** requests access to set your **emoji status**. You will be able to revoke this access in the profile page of **%2$@**.";
"WebApp.EmojiPermission.Allow" = "Allow";
"WebApp.EmojiPermission.Decline" = "Decline";
"WebApp.EmojiPermission.Succeed" = "**%@** can now set your emoji status anytime.";
"WebApp.EmojiPermission.Undo" = "Undo";
"WebApp.LocationPermission.Text" = "**%1$@** requests access to your **location**. You will be able to revoke this access in the profile page of **%2$@**.";
"WebApp.LocationPermission.Allow" = "Allow";
"WebApp.LocationPermission.Decline" = "Decline";
"WebApp.LocationPermission.Succeed" = "**%@** can now have access to your location.";
"WebApp.LocationPermission.Undo" = "Undo";
"WebApp.Download.Photo" = "Download Photo";
"WebApp.Download.Video" = "Download Video";
"WebApp.Download.Document" = "Download Document";
"WebApp.Download.Text" = "**%1$@** suggests you to download **%2$@**%3$@.";
"WebApp.Download.Download" = "Download";
"WebApp.Download.Cancel" = "Cancel";
"WebApp.Download.SavedToPhotos" = "Saved to Photos.";
"WebApp.Download.SavedToFiles" = "Saved to Files.";

View File

@ -44,3 +44,27 @@ func _internal_getPreparedInlineMessage(account: Account, botId: EnginePeer.Id,
}
}
}
func _internal_checkBotDownload(account: Account, botId: EnginePeer.Id, fileName: String, url: String) -> Signal<Bool, NoError> {
return account.postbox.transaction { transaction -> Api.InputUser? in
return transaction.getPeer(botId).flatMap(apiInputUser)
}
|> mapToSignal { inputBot -> Signal<Bool, NoError> in
guard let inputBot else {
return .single(false)
}
return account.network.request(Api.functions.bots.checkDownloadFileParams(bot: inputBot, fileName: fileName, url: url))
|> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse)
}
|> map { value in
switch value {
case .boolTrue:
return true
case .boolFalse:
return false
}
}
}
}

View File

@ -410,6 +410,10 @@ public extension TelegramEngine {
public func getPreparedInlineMessage(botId: EnginePeer.Id, id: String) -> Signal<PreparedInlineMessage?, NoError> {
return _internal_getPreparedInlineMessage(account: self.account, botId: botId, id: id)
}
public func checkBotDownload(botId: EnginePeer.Id, fileName: String, url: String) -> Signal<Bool, NoError> {
return _internal_checkBotDownload(account: self.account, botId: botId, fileName: fileName, url: url)
}
public func requestCancelLiveLocation(ids: [MessageId]) -> Signal<Never, NoError> {
return self.account.postbox.transaction { transaction -> Void in

View File

@ -1544,14 +1544,12 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
}
if let _ = user.botInfo {
//TODO:localize
var canManageEmojiStatus = false
if let cachedData = data.cachedData as? CachedUserData, cachedData.flags.contains(.botCanManageEmojiStatus) {
canManageEmojiStatus = true
}
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
items[.permissions]!.append(PeerInfoScreenSwitchItem(id: 31, text: presentationData.strings.PeerInfo_Permissions_EmojiStatus, 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()
@ -1560,17 +1558,21 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese
}.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
items[.permissions]!.append(PeerInfoScreenSwitchItem(id: 32, text: presentationData.strings.PeerInfo_Permissions_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), emojiStatus: current?.emojiStatus)
}.startStandalone()
}))
}
if !"".isEmpty {
items[.permissions]!.append(PeerInfoScreenSwitchItem(id: 33, text: presentationData.strings.PeerInfo_Permissions_Biometry, value: true, icon: UIImage(bundleImageName: "Settings/Menu/TouchId"), isLocked: false, toggled: { value in
}))
}
if !items[.permissions]!.isEmpty {
items[.permissions]!.insert(PeerInfoScreenHeaderItem(id: 30, text: "ALLOW ACCESS TO"), at: 0)
items[.permissions]!.insert(PeerInfoScreenHeaderItem(id: 30, text: presentationData.strings.PeerInfo_Permissions_Title), at: 0)
}
}

View File

@ -1792,8 +1792,7 @@ private final class PeersCountPanelNode: ASDisplayNode {
var count: Int = 0 {
didSet {
if self.count != oldValue && self.count > 0 {
//TODO:localize
self.button.title = "Send"
self.button.title = self.strings.ShareMenu_Send
self.button.badge = "\(self.count)"
if let (width, sideInset, bottomInset) = self.validLayout {

View File

@ -725,10 +725,9 @@ private final class StarsTransactionSheetContent: CombinedComponent {
let title: String
if isSubscription {
if isBotSubscription {
//TODO:localize
title = "Bot"
title = strings.Stars_Transaction_Subscription_Bot
} else if isBusinessSubscription {
title = "Business"
title = strings.Stars_Transaction_Subscription_Business
} else {
title = strings.Stars_Transaction_Subscription_Subscription
}
@ -766,10 +765,9 @@ private final class StarsTransactionSheetContent: CombinedComponent {
)
))
if case let .subscription(subscription) = component.subject, let title = subscription.title {
//TODO:localize
tableItems.append(.init(
id: "subscription",
title: "Subscription",
title: strings.Stars_Transaction_Subscription,
component: AnyComponent(
MultilineTextComponent(text: .plain(NSAttributedString(string: title, font: tableFont, textColor: tableTextColor)))
)

View File

@ -362,7 +362,7 @@ private final class SheetContent: CombinedComponent {
let titleString: String
if isSubscription {
if isBot {
titleString = "Subscription Name"
titleString = component.invoice.title
} else {
titleString = strings.Stars_Transfer_Subscribe_Channel_Title
}
@ -412,7 +412,7 @@ private final class SheetContent: CombinedComponent {
if case .starsChatSubscription = context.component.source {
infoText = strings.Stars_Transfer_SubscribeInfo(state.botPeer?.compactDisplayTitle ?? "", strings.Stars_Transfer_Info_Stars(Int32(amount))).string
} else if let _ = component.invoice.subscriptionPeriod {
infoText = "Do you want to subscribe to **\(component.invoice.title)** in **\(state.botPeer?.compactDisplayTitle ?? "")** for **\(strings.Stars_Transfer_Info_Stars(Int32(amount)))** per month?"
infoText = strings.Stars_Transfer_BotSubscribeInfo(component.invoice.title, state.botPeer?.compactDisplayTitle ?? "", strings.Stars_Transfer_BotSubscribeInfo_Stars(Int32(amount))).string
} else if !component.extendedMedia.isEmpty {
var description: String = ""
var photoCount: Int32 = 0
@ -534,11 +534,10 @@ private final class SheetContent: CombinedComponent {
let amountString = presentationStringsFormattedNumber(Int32(amount), presentationData.dateTimeFormat.groupingSeparator)
let buttonAttributedString: NSMutableAttributedString
if case .starsChatSubscription = component.source {
//TODO:localize
buttonAttributedString = NSMutableAttributedString(string: "Subscribe for # \(amountString) / month", font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)
//buttonAttributedString = NSMutableAttributedString(string: strings.Stars_Transfer_Subscribe, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)
buttonAttributedString = NSMutableAttributedString(string: "\(strings.Stars_Transfer_SubscribeFor) # \(amountString) \(strings.Stars_Transfer_SubscribePerMonth)", font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)
} else if let _ = component.invoice.subscriptionPeriod {
buttonAttributedString = NSMutableAttributedString(string: "Subscribe for # \(amountString) / month", font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)
buttonAttributedString = NSMutableAttributedString(string: "\(strings.Stars_Transfer_SubscribeFor) # \(amountString) \(strings.Stars_Transfer_SubscribePerMonth)", font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)
} else {
buttonAttributedString = NSMutableAttributedString(string: "\(strings.Stars_Transfer_Pay) # \(amountString)", font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center)
}

View File

@ -49,6 +49,7 @@ swift_library(
"//submodules/TelegramUI/Components/ListSectionComponent",
"//submodules/TelegramUI/Components/Chat/ChatMessageItemImpl",
"//submodules/DeviceLocationManager",
"//submodules/DeviceAccess",
"//submodules/TelegramUI/Components/Utils/GenerateStickerPlaceholderImage",
],
visibility = [

View File

@ -33,6 +33,7 @@ import AvatarNode
import OverlayStatusController
import TelegramUIPreferences
import CoreMotion
import DeviceAccess
import DeviceLocationManager
import LegacyMediaPickerUI
import GenerateStickerPlaceholderImage
@ -1205,7 +1206,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
}
}
case "web_app_open_popup":
if let json = json, let message = json["message"] as? String, let buttons = json["buttons"] as? [Any] {
if let json, let message = json["message"] as? String, let buttons = json["buttons"] as? [Any] {
let presentationData = self.presentationData
let title = json["title"] as? String
@ -1262,12 +1263,12 @@ public final class WebAppController: ViewController, AttachmentContainable {
self.controller?.present(alertController, in: .window(.root))
}
case "web_app_setup_closing_behavior":
if let json = json, let needConfirmation = json["need_confirmation"] as? Bool {
if let json, let needConfirmation = json["need_confirmation"] as? Bool {
self.needDismissConfirmation = needConfirmation
}
case "web_app_open_scan_qr_popup":
var info: String = ""
if let json = json, let text = json["text"] as? String {
if let json, let text = json["text"] as? String {
info = text
}
let controller = QrCodeScanScreen(context: self.context, subject: .custom(info: info))
@ -1288,7 +1289,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
controller.dismissAnimated()
}
case "web_app_read_text_from_clipboard":
if let json = json, let requestId = json["req_id"] as? String {
if let json, let requestId = json["req_id"] as? String {
let botId = controller.botId
let isAttachMenu = controller.url == nil
@ -1327,7 +1328,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
self.invokeCustomMethod(requestId: requestId, method: method, params: paramsString ?? "{}")
}
case "web_app_setup_settings_button":
if let json = json, let isVisible = json["is_visible"] as? Bool {
if let json, let isVisible = json["is_visible"] as? Bool {
self.controller?.hasSettings = isVisible
}
case "web_app_biometry_get_info":
@ -1353,11 +1354,11 @@ public final class WebAppController: ViewController, AttachmentContainable {
self.openBotSettings()
}
case "web_app_setup_swipe_behavior":
if let json = json, let isPanGestureEnabled = json["allow_vertical_swipe"] as? Bool {
if let json, let isPanGestureEnabled = json["allow_vertical_swipe"] as? Bool {
self.controller?._isPanGestureEnabled = isPanGestureEnabled
}
case "web_app_share_to_story":
if let json = json, let mediaUrl = json["media_url"] as? String {
if let json, let mediaUrl = json["media_url"] as? String {
let text = json["text"] as? String
let link = json["widget_link"] as? [String: Any]
@ -1441,25 +1442,29 @@ public final class WebAppController: ViewController, AttachmentContainable {
case "web_app_exit_fullscreen":
self.setIsFullscreen(false)
case "web_app_start_accelerometer":
if let json = json, let refreshRate = json["refresh_rate"] as? Double {
if let json {
let refreshRate = json["refresh_rate"] as? Double
self.setIsAccelerometerActive(true, refreshRate: refreshRate)
}
case "web_app_stop_accelerometer":
self.setIsAccelerometerActive(false)
case "web_app_start_device_orientation":
if let json = json, let refreshRate = json["refresh_rate"] as? Double {
self.setIsDeviceOrientationActive(true, refreshRate: refreshRate)
if let json {
let refreshRate = json["refresh_rate"] as? Double
let absolute = (json["need_absolute"] as? Bool) == true
self.setIsDeviceOrientationActive(true, refreshRate: refreshRate, absolute: absolute)
}
case "web_app_stop_device_orientation":
self.setIsDeviceOrientationActive(false)
case "web_app_start_gyroscope":
if let json = json, let refreshRate = json["refresh_rate"] as? Double {
if let json {
let refreshRate = json["refresh_rate"] as? Double
self.setIsGyroscopeActive(true, refreshRate: refreshRate)
}
case "web_app_stop_gyroscope":
self.setIsGyroscopeActive(false)
case "web_app_set_emoji_status":
if let json = json, let emojiIdString = json["custom_emoji_id"] as? String, let emojiId = Int64(emojiIdString) {
if let json, let emojiIdString = json["custom_emoji_id"] as? String, let emojiId = Int64(emojiIdString) {
let duration = json["duration"] as? Double
self.setEmojiStatus(emojiId, duration: duration.flatMap { Int32($0) })
}
@ -1479,17 +1484,17 @@ public final class WebAppController: ViewController, AttachmentContainable {
self.openLocationSettings()
}
case "web_app_send_prepared_message":
if let json = json, let id = json["id"] as? String {
if let json, let id = json["id"] as? String {
self.sendPreparedMessage(id: id)
}
case "web_app_request_emoji_status_access":
self.requestEmojiStatusAccess()
case "web_app_request_file_download":
if let json = json, let url = json["url"] as? String, let fileName = json["file_name"] as? String {
if let json, let url = json["url"] as? String, let fileName = json["file_name"] as? String {
self.downloadFile(url: url, fileName: fileName)
}
case "web_app_toggle_orientation_lock":
if let json = json, let lock = json["locked"] as? Bool {
if let json, let lock = json["locked"] as? Bool {
controller.parentController()?.lockOrientation = lock
}
default:
@ -2194,6 +2199,8 @@ public final class WebAppController: ViewController, AttachmentContainable {
if let refreshRate {
self.motionManager.accelerometerUpdateInterval = refreshRate * 0.001
} else {
self.motionManager.accelerometerUpdateInterval = 1.0
}
self.motionManager.startAccelerometerUpdates(to: OperationQueue.main) { [weak self] data, error in
guard let self, let data else {
@ -2214,7 +2221,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
}
private var isDeviceOrientationActive = false
fileprivate func setIsDeviceOrientationActive(_ isActive: Bool, refreshRate: Double? = nil) {
fileprivate func setIsDeviceOrientationActive(_ isActive: Bool, refreshRate: Double? = nil, absolute: Bool = false) {
guard self.motionManager.isDeviceMotionAvailable else {
self.webView?.sendEvent(name: "device_orientation_failed", data: "{error: \"UNSUPPORTED\"}")
return
@ -2228,16 +2235,39 @@ public final class WebAppController: ViewController, AttachmentContainable {
if let refreshRate {
self.motionManager.deviceMotionUpdateInterval = refreshRate * 0.001
} else {
self.motionManager.deviceMotionUpdateInterval = 1.0
}
self.motionManager.startDeviceMotionUpdates(using: .xTrueNorthZVertical, to: OperationQueue.main) { [weak self] data, error in
let referenceFrame: CMAttitudeReferenceFrame
if absolute && CMMotionManager.availableAttitudeReferenceFrames().contains(.xMagneticNorthZVertical) {
referenceFrame = .xMagneticNorthZVertical
} else {
if CMMotionManager.availableAttitudeReferenceFrames().contains(.xArbitraryCorrectedZVertical) {
referenceFrame = .xArbitraryCorrectedZVertical
} else {
referenceFrame = .xArbitraryZVertical
}
}
self.motionManager.startDeviceMotionUpdates(using: referenceFrame, to: OperationQueue.main) { [weak self] data, error in
guard let self, let data else {
return
}
var alpha: Double
if absolute {
alpha = data.heading * .pi / 180.0
if alpha > .pi {
alpha -= 2.0 * .pi
} else if alpha < -.pi {
alpha += 2.0 * .pi
}
} else {
alpha = data.attitude.yaw
}
self.webView?.sendEvent(
name: "device_orientation_changed",
data: "{alpha: \(data.attitude.yaw), beta: \(data.attitude.pitch), gamma: \(data.attitude.roll)}"
data: "{absolute: true, alpha: \(alpha), beta: \(data.attitude.pitch), gamma: \(data.attitude.roll)}"
)
print("{alpha: \(data.attitude.yaw), beta: \(data.attitude.pitch), gamma: \(data.attitude.roll)}")
}
} else {
if self.motionManager.isDeviceMotionActive {
@ -2262,6 +2292,8 @@ public final class WebAppController: ViewController, AttachmentContainable {
if let refreshRate {
self.motionManager.gyroUpdateInterval = refreshRate * 0.001
} else {
self.motionManager.gyroUpdateInterval = 1.0
}
self.motionManager.startGyroUpdates(to: OperationQueue.main) { [weak self] data, error in
guard let self, let data else {
@ -2309,47 +2341,52 @@ public final class WebAppController: ViewController, AttachmentContainable {
guard let controller = self.controller else {
return
}
var isMedia = true
var isMedia = false
var title: String?
let photoExtensions = [".jpg", ".png", ".gif", ".tiff"]
let videoExtensions = [".mp4", ".mov"]
let lowercasedFilename = fileName.lowercased()
for ext in photoExtensions {
if lowercasedFilename.hasSuffix(ext) {
title = "Download Photo"
title = self.presentationData.strings.WebApp_Download_Photo
isMedia = true
break
}
}
if title == nil {
for ext in videoExtensions {
if lowercasedFilename.hasSuffix(ext) {
title = "Download Video"
title = self.presentationData.strings.WebApp_Download_Video
break
}
}
}
if title == nil {
title = "Download Document"
isMedia = false
title = self.presentationData.strings.WebApp_Download_Document
}
let _ = (FileDownload.getFileSize(url: url)
|> deliverOnMainQueue).start(next: { [weak self] fileSize in
let _ = combineLatest(queue: Queue.mainQueue(),
FileDownload.getFileSize(url: url),
self.context.engine.messages.checkBotDownload(botId: controller.botId, fileName: fileName, url: url)
).start(next: { [weak self] fileSize, canDownload in
guard let self else {
return
}
guard canDownload else {
self.webView?.sendEvent(name: "file_download_requested", data: "{status: \"cancelled\"}")
return
}
var fileSizeString = ""
if let fileSize {
fileSizeString = " (\(dataSizeString(fileSize, formatting: DataSizeStringFormatting(presentationData: self.presentationData))))"
}
let text: String = "**\(controller.botName)** suggests you to download **\(fileName)**\(fileSizeString)."
let text: String = self.presentationData.strings.WebApp_Download_Text(controller.botName, fileName, fileSizeString).string
let alertController = standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: title, text: text, actions: [
TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_Cancel, action: { [weak self] in
self?.webView?.sendEvent(name: "file_download_requested", data: "{status: \"cancelled\"}")
}),
TextAlertAction(type: .defaultAction, title: "Download", action: { [weak self] in
TextAlertAction(type: .defaultAction, title: self.presentationData.strings.WebApp_Download_Download, action: { [weak self] in
self?.startDownload(url: url, fileName: fileName, fileSize: fileSize, isMedia: isMedia)
})
], parseMarkdown: true)
@ -2387,12 +2424,12 @@ public final class WebAppController: ViewController, AttachmentContainable {
progress: progress,
title: fileName,
text: text,
undoText: "Cancel"
undoText: self.presentationData.strings.WebApp_Download_Cancel
)
},
completion: { [weak self] resultUrl, _ in
if let resultUrl, let self {
let tooltipContent: UndoOverlayContent = .actionSucceeded(title: fileName, text: isMedia ? "Saved to Photos" : "Saved to Files", cancel: nil, destructive: false)
let tooltipContent: UndoOverlayContent = .actionSucceeded(title: fileName, text: isMedia ? self.presentationData.strings.WebApp_Download_SavedToPhotos : self.presentationData.strings.WebApp_Download_SavedToFiles, cancel: nil, destructive: false)
if isMedia {
let saveToPhotos: (URL, Bool) -> Void = { url, isVideo in
var fileExtension = (resultUrl.absoluteString as NSString).pathExtension
@ -2470,7 +2507,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
progress: 0.0,
title: fileName,
text: text,
undoText: "Cancel"
undoText: self.presentationData.strings.WebApp_Download_Cancel
),
elevatedLayout: false,
position: .top,
@ -2534,12 +2571,11 @@ 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(
presentationData: presentationData,
content: .invitedToVoiceChat(context: self.context, peer: botPeer, title: nil, text: "**\(controller.botName)** can now set your emoji status anytime.", action: "Undo", duration: 5.0),
presentationData: self.presentationData,
content: .invitedToVoiceChat(context: self.context, peer: botPeer, title: nil, text: self.presentationData.strings.WebApp_EmojiPermission_Succeed(controller.botName).string, action: self.presentationData.strings.WebApp_EmojiPermission_Undo, duration: 5.0),
elevatedLayout: true,
action: { action in
if case .undo = action {
@ -2618,10 +2654,16 @@ public final class WebAppController: ViewController, AttachmentContainable {
|> deliverOnMainQueue).start(completed: { [weak self] in
self?.webView?.sendEvent(name: "emoji_status_set", data: nil)
})
//TODO:localize
let text: String
if let duration {
let durationString = scheduledTimeIntervalString(strings: self.presentationData.strings, value: duration)
text = self.presentationData.strings.WebApp_Emoji_DurationSucceed(durationString).string
} else {
text = self.presentationData.strings.WebApp_Emoji_Succeed
}
let resultController = UndoOverlayController(
presentationData: self.presentationData,
content: .sticker(context: context, file: file, loop: false, title: nil, text: "Your emoji status updated.", undoText: nil, customAction: nil),
content: .sticker(context: context, file: file, loop: false, title: nil, text: text, undoText: nil, customAction: nil),
elevatedLayout: true,
action: { action in
if case .undo = action {
@ -2709,108 +2751,116 @@ public final class WebAppController: ViewController, AttachmentContainable {
})
}
private let locationManager = LocationManager()
fileprivate func requestLocation() {
guard let controller = self.controller else {
return
}
let context = controller.context
let botId = controller.botId
let _ = (webAppPermissionsState(context: self.context, peerId: botId)
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self, weak controller] state in
guard let self else {
let context = self.context
DeviceAccess.authorizeAccess(to: .location(.send), locationManager: self.locationManager, presentationData: self.presentationData, present: { [weak self] c, a in
self?.controller?.present(c, in: .window(.root), with: a)
}, openSettings: {
context.sharedContext.applicationBindings.openSettings()
}, { [weak self, weak controller] authorized in
guard let controller else {
return
}
var shouldRequest = false
if let location = state?.location {
if location.isRequested {
if location.isAllowed {
let locationCoordinates = Signal<CLLocation, NoError> { subscriber in
return context.sharedContext.locationManager!.push(mode: DeviceLocationMode.preciseForeground, updated: { location, _ in
subscriber.putNext(location)
subscriber.putCompletion()
let context = controller.context
let botId = controller.botId
let _ = (webAppPermissionsState(context: context, peerId: botId)
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self, weak controller] state in
guard let self else {
return
}
var shouldRequest = false
if let location = state?.location {
if location.isRequested {
if location.isAllowed {
let locationCoordinates = Signal<CLLocation, NoError> { subscriber in
return context.sharedContext.locationManager!.push(mode: DeviceLocationMode.preciseForeground, updated: { location, _ in
subscriber.putNext(location)
subscriber.putCompletion()
})
} |> deliverOnMainQueue
let _ = locationCoordinates.startStandalone(next: { location in
var data: [String: Any] = [:]
data["available"] = true
data["latitude"] = location.coordinate.latitude
data["longitude"] = location.coordinate.longitude
data["altitude"] = location.altitude
data["course"] = location.course
data["speed"] = location.speed
data["horizontal_accuracy"] = location.horizontalAccuracy
data["vertical_accuracy"] = location.verticalAccuracy
if #available(iOS 13.4, *) {
data["course_accuracy"] = location.courseAccuracy
} else {
data["course_accuracy"] = NSNull()
}
data["speed_accuracy"] = location.speedAccuracy
if let serializedData = JSON(dictionary: data)?.string {
self.webView?.sendEvent(name: "location_requested", data: serializedData)
}
})
} |> deliverOnMainQueue
let _ = locationCoordinates.startStandalone(next: { location in
} else {
var data: [String: Any] = [:]
data["available"] = true
data["latitude"] = location.coordinate.latitude
data["longitude"] = location.coordinate.longitude
data["altitude"] = location.altitude
data["course"] = location.course
data["speed"] = location.speed
data["horizontal_accuracy"] = location.horizontalAccuracy
data["vertical_accuracy"] = location.verticalAccuracy
if #available(iOS 13.4, *) {
data["course_accuracy"] = location.courseAccuracy
} else {
data["course_accuracy"] = NSNull()
}
data["speed_accuracy"] = location.speedAccuracy
if let serializedData = JSON(dictionary: data)?.string {
self.webView?.sendEvent(name: "location_requested", data: serializedData)
}
})
data["available"] = false
self.webView?.sendEvent(name: "location_requested", data: JSON(dictionary: data)?.string)
}
} else {
var data: [String: Any] = [:]
data["available"] = false
self.webView?.sendEvent(name: "location_requested", data: JSON(dictionary: data)?.string)
shouldRequest = true
}
} else {
shouldRequest = true
}
} else {
shouldRequest = true
}
if shouldRequest {
let _ = (context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId),
TelegramEngine.EngineData.Item.Peer.Peer(id: botId)
)
|> deliverOnMainQueue).start(next: { [weak self, weak controller] accountPeer, botPeer in
guard let accountPeer, let botPeer, let controller else {
return
}
let alertController = webAppLocationAlertController(
context: controller.context,
accountPeer: accountPeer,
botPeer: botPeer,
completion: { [weak self, weak controller] result in
guard let self, let controller else {
return
}
if result {
let resultController = UndoOverlayController(
presentationData: self.presentationData,
content: .invitedToVoiceChat(context: context, peer: botPeer, title: nil, text: "**\(botPeer.compactDisplayTitle)** can now have access to your location.", action: "Undo", duration: 5.0),
elevatedLayout: true,
action: { action in
if case .undo = action {
}
return true
}
)
controller.present(resultController, in: .window(.root))
Queue.mainQueue().after(0.1, {
self.requestLocation()
})
} else {
var data: [String: Any] = [:]
data["available"] = false
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), emojiStatus: current?.emojiStatus)
}.start()
}
if shouldRequest {
let _ = (context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId),
TelegramEngine.EngineData.Item.Peer.Peer(id: botId)
)
controller.present(alertController, in: .window(.root))
})
}
|> deliverOnMainQueue).start(next: { [weak self, weak controller] accountPeer, botPeer in
guard let accountPeer, let botPeer, let controller else {
return
}
let alertController = webAppLocationAlertController(
context: controller.context,
accountPeer: accountPeer,
botPeer: botPeer,
completion: { [weak self, weak controller] result in
guard let self, let controller else {
return
}
if result {
let resultController = UndoOverlayController(
presentationData: self.presentationData,
content: .invitedToVoiceChat(context: context, peer: botPeer, title: nil, text: self.presentationData.strings.WebApp_LocationPermission_Succeed(botPeer.compactDisplayTitle).string, action: self.presentationData.strings.WebApp_LocationPermission_Undo, duration: 5.0),
elevatedLayout: true,
action: { action in
if case .undo = action {
}
return true
}
)
controller.present(resultController, in: .window(.root))
Queue.mainQueue().after(0.1, {
self.requestLocation()
})
} else {
var data: [String: Any] = [:]
data["available"] = false
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), emojiStatus: current?.emojiStatus)
}.start()
}
)
controller.present(alertController, in: .window(.root))
})
}
})
})
}
}
@ -3094,9 +3144,8 @@ 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
items.append(.action(ContextMenuActionItem(text: presentationData.strings.WebApp_AddToHomeScreen, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/AddSquare"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] c, _ in
c?.dismiss(completion: nil)

View File

@ -192,8 +192,7 @@ private final class WebAppEmojiStatusAlertContentNode: AlertContentNode {
}
override func updateTheme(_ theme: AlertControllerTheme) {
//TODO:localize
let string = "**\(self.botName)** requests access to set your **emoji status**. You will be able to revoke this access in the profile page of **\(self.botName)**."
let string = self.strings.WebApp_EmojiPermission_Text(self.botName, self.botName).string
let attributedText = parseMarkdownIntoAttributedString(string, attributes: MarkdownAttributes(
body: MarkdownAttributeSet(font: Font.regular(13.0), textColor: theme.primaryColor),
bold: MarkdownAttributeSet(font: Font.semibold(13.0), textColor: theme.primaryColor),
@ -340,11 +339,11 @@ func webAppEmojiStatusAlertController(
var dismissImpl: ((Bool) -> Void)?
var contentNode: WebAppEmojiStatusAlertContentNode?
let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: "Decline", action: {
let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: strings.WebApp_EmojiPermission_Decline, action: {
dismissImpl?(true)
completion(false)
}), TextAlertAction(type: .defaultAction, title: "Allow", action: {
}), TextAlertAction(type: .defaultAction, title: strings.WebApp_EmojiPermission_Allow, action: {
dismissImpl?(true)
completion(true)

View File

@ -265,16 +265,15 @@ private final class WebAppLocationAlertContentNode: AlertContentNode {
func webAppLocationAlertController(context: AccountContext, accountPeer: EnginePeer, botPeer: EnginePeer, completion: @escaping (Bool) -> Void) -> AlertController {
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let strings = presentationData.strings
//TODO:localize
let text = "**\(botPeer.compactDisplayTitle)** requests access to your **location**. You will be able to revoke this access in the profile page of **\(botPeer.compactDisplayTitle)**."
let text = strings.WebApp_LocationPermission_Text(botPeer.compactDisplayTitle, botPeer.compactDisplayTitle).string
var dismissImpl: ((Bool) -> Void)?
var contentNode: WebAppLocationAlertContentNode?
let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: "Decline", action: {
let actions: [TextAlertAction] = [TextAlertAction(type: .genericAction, title: strings.WebApp_LocationPermission_Decline, action: {
dismissImpl?(true)
completion(false)
}), TextAlertAction(type: .defaultAction, title: "Allow", action: {
}), TextAlertAction(type: .defaultAction, title: strings.WebApp_LocationPermission_Allow, action: {
dismissImpl?(true)
completion(true)
})]

View File

@ -83,6 +83,7 @@ private final class SheetContent: CombinedComponent {
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
let theme = presentationData.theme
let strings = presentationData.strings
var contentSize = CGSize(width: context.availableSize.width, height: 18.0)
@ -134,7 +135,7 @@ private final class SheetContent: CombinedComponent {
contentSize.height += 128.0
let title = title.update(
component: Text(text: "Set Emoji Status", font: Font.bold(24.0), color: theme.list.itemPrimaryTextColor),
component: Text(text: strings.WebApp_Emoji_Title, font: Font.bold(24.0), color: theme.list.itemPrimaryTextColor),
availableSize: CGSize(width: constrainedTitleWidth, height: context.availableSize.height),
transition: .immediate
)
@ -153,11 +154,11 @@ private final class SheetContent: CombinedComponent {
})
var textString: String
if let _ = component.duration {
//TODO:localize
textString = "Do you want to set this emoji status suggested by **\(component.botName)** for **5 minutes**?"
if let duration = component.duration {
let durationString = scheduledTimeIntervalString(strings: strings, value: duration)
textString = strings.WebApp_Emoji_DurationText(component.botName, durationString).string
} else {
textString = "Do you want to set this emoji status suggested by **\(component.botName)**?"
textString = strings.WebApp_Emoji_Text(component.botName).string
}
let text = text.update(
@ -207,7 +208,7 @@ private final class SheetContent: CombinedComponent {
),
content: AnyComponentWithIdentity(
id: AnyHashable(0),
component: AnyComponent(MultilineTextComponent(text: .plain(NSMutableAttributedString(string: "Confirm", font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center))))
component: AnyComponent(MultilineTextComponent(text: .plain(NSMutableAttributedString(string: strings.WebApp_Emoji_Confirm, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center))))
),
isEnabled: true,
displaysProgress: false,

View File

@ -1,5 +1,5 @@
{
"app": "11.3.1",
"app": "11.4",
"xcode": "16.0",
"bazel": "7.3.1:981f82a470bad1349322b6f51c9c6ffa0aa291dab1014fac411543c12e661dff",
"macos": "15.0"