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
40080bbe1f
commit
490d0f8ea7
@ -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.";
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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)))
|
||||
)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 = [
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
})]
|
||||
|
@ -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,
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"app": "11.3.1",
|
||||
"app": "11.4",
|
||||
"xcode": "16.0",
|
||||
"bazel": "7.3.1:981f82a470bad1349322b6f51c9c6ffa0aa291dab1014fac411543c12e661dff",
|
||||
"macos": "15.0"
|
||||
|
Loading…
x
Reference in New Issue
Block a user