Merge branch 'master' into experiments/string-generation
4
.github/workflows/build.yml
vendored
@ -48,9 +48,11 @@ jobs:
|
||||
|
||||
cd $SOURCE_DIR
|
||||
|
||||
BUILD_NUMBER_OFFSET="$(cat build_number_offset)"
|
||||
|
||||
export APP_VERSION=$(cat versions.json | python3 -c 'import json,sys;obj=json.load(sys.stdin);print(obj["app"]);')
|
||||
export COMMIT_COUNT=$(git rev-list --count HEAD)
|
||||
export COMMIT_COUNT="$(($COMMIT_COUNT+2000))"
|
||||
export COMMIT_COUNT="$(($COMMIT_COUNT+$BUILD_NUMBER_OFFSET))"
|
||||
export BUILD_NUMBER="$COMMIT_COUNT"
|
||||
echo "BUILD_NUMBER=$(echo $BUILD_NUMBER)" >> $GITHUB_ENV
|
||||
echo "APP_VERSION=$(echo $APP_VERSION)" >> $GITHUB_ENV
|
||||
|
@ -70,6 +70,7 @@ beta_testflight:
|
||||
stage: build
|
||||
only:
|
||||
- beta
|
||||
- hotfix
|
||||
except:
|
||||
- tags
|
||||
script:
|
||||
@ -87,6 +88,7 @@ deploy_beta_testflight:
|
||||
stage: deploy
|
||||
only:
|
||||
- beta
|
||||
- hotfix
|
||||
except:
|
||||
- tags
|
||||
script:
|
||||
@ -100,6 +102,7 @@ verifysanity_beta_testflight:
|
||||
stage: verifysanity
|
||||
only:
|
||||
- beta
|
||||
- hotfix
|
||||
except:
|
||||
- tags
|
||||
script:
|
||||
@ -118,6 +121,7 @@ verify_beta_testflight:
|
||||
stage: verify
|
||||
only:
|
||||
- beta
|
||||
- hotfix
|
||||
except:
|
||||
- tags
|
||||
script:
|
||||
|
@ -1 +1 @@
|
||||
E65Wt9QZyVD8tvGhCJD3My6x57eDORYaiYh6HR7T3fK=
|
||||
4f0d2d13a70664d3029d9b97935089df0426fe53745965d175408752838b80dd
|
142
Telegram/BUILD
@ -277,6 +277,9 @@ official_apple_pay_merchants = [
|
||||
"merchant.sberbank.test.ph.telegra.Telegraph",
|
||||
"merchant.privatbank.test.telergramios",
|
||||
"merchant.privatbank.prod.telergram",
|
||||
"merchant.paymaster.test.telegramios",
|
||||
"merchant.smartglocal.prod.telegramios",
|
||||
"merchant.smartglocal.test.telegramios",
|
||||
]
|
||||
|
||||
official_bundle_ids = [
|
||||
@ -1430,6 +1433,96 @@ ios_extension(
|
||||
],
|
||||
)
|
||||
|
||||
plist_fragment(
|
||||
name = "BroadcastUploadInfoPlist",
|
||||
extension = "plist",
|
||||
template =
|
||||
"""
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>{telegram_bundle_id}.BroadcastUpload</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>Telegram</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.broadcast-services-upload</string>
|
||||
<key>NSExtensionPrincipalClass</key>
|
||||
<string>BroadcastUploadSampleHandler</string>
|
||||
<key>RPBroadcastProcessMode</key>
|
||||
<string>RPBroadcastProcessModeSampleBuffer</string>
|
||||
</dict>
|
||||
""".format(
|
||||
telegram_bundle_id = telegram_bundle_id,
|
||||
)
|
||||
)
|
||||
|
||||
swift_library(
|
||||
name = "BroadcastUploadExtensionLib",
|
||||
module_name = "BroadcastUploadExtensionLib",
|
||||
srcs = glob([
|
||||
"BroadcastUpload/**/*.swift",
|
||||
]),
|
||||
deps = [
|
||||
"//submodules/TelegramUI:TelegramUI",
|
||||
"//submodules/TelegramVoip:TelegramVoip",
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/BuildConfig:BuildConfig",
|
||||
"//submodules/WidgetItems:WidgetItems",
|
||||
"//submodules/BroadcastUploadHelpers:BroadcastUploadHelpers",
|
||||
],
|
||||
)
|
||||
|
||||
genrule(
|
||||
name = "SetMinOsVersionBroadcastUploadExtension",
|
||||
cmd_bash =
|
||||
"""
|
||||
name=BroadcastUploadExtension.appex
|
||||
cat $(location PatchMinOSVersion.source.sh) | sed -e "s/<<<MIN_OS_VERSION>>>/11\\.0/g" | sed -e "s/<<<NAME>>>/$$name/g" > $(location SetMinOsVersionBroadcastUploadExtension.sh)
|
||||
""",
|
||||
srcs = [
|
||||
"PatchMinOSVersion.source.sh",
|
||||
],
|
||||
outs = [
|
||||
"SetMinOsVersionBroadcastUploadExtension.sh",
|
||||
],
|
||||
executable = True,
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
]
|
||||
)
|
||||
|
||||
ios_extension(
|
||||
name = "BroadcastUploadExtension",
|
||||
bundle_id = "{telegram_bundle_id}.BroadcastUpload".format(
|
||||
telegram_bundle_id = telegram_bundle_id,
|
||||
),
|
||||
families = [
|
||||
"iphone",
|
||||
"ipad",
|
||||
],
|
||||
infoplists = [
|
||||
":BroadcastUploadInfoPlist",
|
||||
":VersionInfoPlist",
|
||||
":BuildNumberInfoPlist",
|
||||
":AppNameInfoPlist",
|
||||
],
|
||||
minimum_os_version = "9.0", # maintain the same minimum OS version across extensions
|
||||
ipa_post_processor = ":SetMinOsVersionBroadcastUploadExtension",
|
||||
provisioning_profile = select({
|
||||
":disableProvisioningProfilesSetting": None,
|
||||
"//conditions:default": "@build_configuration//provisioning:BroadcastUpload.mobileprovision",
|
||||
}),
|
||||
deps = [":BroadcastUploadExtensionLib"],
|
||||
frameworks = [
|
||||
":TelegramUIFramework",
|
||||
":SwiftSignalKitFramework",
|
||||
],
|
||||
)
|
||||
|
||||
plist_fragment(
|
||||
name = "NotificationServiceInfoPlist",
|
||||
extension = "plist",
|
||||
@ -1718,6 +1811,7 @@ ios_application(
|
||||
":NotificationServiceExtension",
|
||||
":IntentsExtension",
|
||||
":WidgetExtension",
|
||||
":BroadcastUploadExtension",
|
||||
],
|
||||
}),
|
||||
watch_application = select({
|
||||
@ -1729,3 +1823,51 @@ ios_application(
|
||||
":Lib",
|
||||
],
|
||||
)
|
||||
|
||||
# Temporary targets used to simplify webrtc build tests
|
||||
|
||||
ios_application(
|
||||
name = "webrtc_build_test",
|
||||
bundle_id = "{telegram_bundle_id}".format(
|
||||
telegram_bundle_id = telegram_bundle_id,
|
||||
),
|
||||
families = ["iphone", "ipad"],
|
||||
minimum_os_version = "9.0",
|
||||
provisioning_profile = select({
|
||||
":disableProvisioningProfilesSetting": None,
|
||||
"//conditions:default": "@build_configuration//provisioning:Telegram.mobileprovision",
|
||||
}),
|
||||
entitlements = ":TelegramEntitlements.entitlements",
|
||||
infoplists = [
|
||||
":TelegramInfoPlist",
|
||||
":BuildNumberInfoPlist",
|
||||
":VersionInfoPlist",
|
||||
":UrlTypesInfoPlist",
|
||||
],
|
||||
deps = [
|
||||
"//third-party/webrtc:webrtc_lib",
|
||||
],
|
||||
)
|
||||
|
||||
ios_application(
|
||||
name = "libvpx_build_test",
|
||||
bundle_id = "{telegram_bundle_id}".format(
|
||||
telegram_bundle_id = telegram_bundle_id,
|
||||
),
|
||||
families = ["iphone", "ipad"],
|
||||
minimum_os_version = "9.0",
|
||||
provisioning_profile = select({
|
||||
":disableProvisioningProfilesSetting": None,
|
||||
"//conditions:default": "@build_configuration//provisioning:Telegram.mobileprovision",
|
||||
}),
|
||||
entitlements = ":TelegramEntitlements.entitlements",
|
||||
infoplists = [
|
||||
":TelegramInfoPlist",
|
||||
":BuildNumberInfoPlist",
|
||||
":VersionInfoPlist",
|
||||
":UrlTypesInfoPlist",
|
||||
],
|
||||
deps = [
|
||||
"//third-party/libvpx:vpx",
|
||||
],
|
||||
)
|
||||
|
282
Telegram/BroadcastUpload/BroadcastUploadExtension.swift
Normal file
@ -0,0 +1,282 @@
|
||||
import Foundation
|
||||
import ReplayKit
|
||||
import CoreVideo
|
||||
import TelegramVoip
|
||||
import SwiftSignalKit
|
||||
import BuildConfig
|
||||
import BroadcastUploadHelpers
|
||||
import AudioToolbox
|
||||
|
||||
private func rootPathForBasePath(_ appGroupPath: String) -> String {
|
||||
return appGroupPath + "/telegram-data"
|
||||
}
|
||||
|
||||
@available(iOS 10.0, *)
|
||||
@objc(BroadcastUploadSampleHandler) class BroadcastUploadSampleHandler: RPBroadcastSampleHandler {
|
||||
private var screencastBufferClientContext: IpcGroupCallBufferBroadcastContext?
|
||||
private var statusDisposable: Disposable?
|
||||
private var audioConverter: CustomAudioConverter?
|
||||
|
||||
deinit {
|
||||
self.statusDisposable?.dispose()
|
||||
}
|
||||
|
||||
public override func beginRequest(with context: NSExtensionContext) {
|
||||
super.beginRequest(with: context)
|
||||
}
|
||||
|
||||
private func finish(with reason: IpcGroupCallBufferBroadcastContext.Status.FinishReason) {
|
||||
var errorString: String?
|
||||
switch reason {
|
||||
case .callEnded:
|
||||
errorString = "You're not in a voice chat"
|
||||
case .error:
|
||||
errorString = "Finished"
|
||||
case .screencastEnded:
|
||||
break
|
||||
}
|
||||
if let errorString = errorString {
|
||||
let error = NSError(domain: "BroadcastUploadExtension", code: 1, userInfo: [
|
||||
NSLocalizedDescriptionKey: errorString
|
||||
])
|
||||
finishBroadcastWithError(error)
|
||||
} else {
|
||||
finishBroadcastGracefully(self)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override public func broadcastStarted(withSetupInfo setupInfo: [String : NSObject]?) {
|
||||
guard let appBundleIdentifier = Bundle.main.bundleIdentifier, let lastDotRange = appBundleIdentifier.range(of: ".", options: [.backwards]) else {
|
||||
self.finish(with: .error)
|
||||
return
|
||||
}
|
||||
|
||||
let baseAppBundleId = String(appBundleIdentifier[..<lastDotRange.lowerBound])
|
||||
|
||||
let appGroupName = "group.\(baseAppBundleId)"
|
||||
let maybeAppGroupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupName)
|
||||
|
||||
guard let appGroupUrl = maybeAppGroupUrl else {
|
||||
self.finish(with: .error)
|
||||
return
|
||||
}
|
||||
|
||||
let rootPath = rootPathForBasePath(appGroupUrl.path)
|
||||
|
||||
let logsPath = rootPath + "/broadcast-logs"
|
||||
let _ = try? FileManager.default.createDirectory(atPath: logsPath, withIntermediateDirectories: true, attributes: nil)
|
||||
|
||||
let screencastBufferClientContext = IpcGroupCallBufferBroadcastContext(basePath: rootPath + "/broadcast-coordination")
|
||||
self.screencastBufferClientContext = screencastBufferClientContext
|
||||
|
||||
self.statusDisposable = (screencastBufferClientContext.status
|
||||
|> deliverOnMainQueue).start(next: { [weak self] status in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
switch status {
|
||||
case let .finished(reason):
|
||||
strongSelf.finish(with: reason)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override public func broadcastPaused() {
|
||||
}
|
||||
|
||||
override public func broadcastResumed() {
|
||||
}
|
||||
|
||||
override public func broadcastFinished() {
|
||||
}
|
||||
|
||||
override public func processSampleBuffer(_ sampleBuffer: CMSampleBuffer, with sampleBufferType: RPSampleBufferType) {
|
||||
switch sampleBufferType {
|
||||
case RPSampleBufferType.video:
|
||||
processVideoSampleBuffer(sampleBuffer: sampleBuffer)
|
||||
case RPSampleBufferType.audioApp:
|
||||
processAudioSampleBuffer(sampleBuffer: sampleBuffer)
|
||||
case RPSampleBufferType.audioMic:
|
||||
break
|
||||
@unknown default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
private func processVideoSampleBuffer(sampleBuffer: CMSampleBuffer) {
|
||||
guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
|
||||
return
|
||||
}
|
||||
var orientation = CGImagePropertyOrientation.up
|
||||
if #available(iOS 11.0, *) {
|
||||
if let orientationAttachment = CMGetAttachment(sampleBuffer, key: RPVideoSampleOrientationKey as CFString, attachmentModeOut: nil) as? NSNumber {
|
||||
orientation = CGImagePropertyOrientation(rawValue: orientationAttachment.uint32Value) ?? .up
|
||||
}
|
||||
}
|
||||
if let data = serializePixelBuffer(buffer: pixelBuffer) {
|
||||
self.screencastBufferClientContext?.setCurrentFrame(data: data, orientation: orientation)
|
||||
}
|
||||
}
|
||||
|
||||
private func processAudioSampleBuffer(sampleBuffer: CMSampleBuffer) {
|
||||
guard let formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer) else {
|
||||
return
|
||||
}
|
||||
guard let asbd = CMAudioFormatDescriptionGetStreamBasicDescription(formatDescription) else {
|
||||
return
|
||||
}
|
||||
/*guard let blockBuffer = CMSampleBufferGetDataBuffer(sampleBuffer) else {
|
||||
return
|
||||
}*/
|
||||
|
||||
let format = CustomAudioConverter.Format(
|
||||
numChannels: Int(asbd.pointee.mChannelsPerFrame),
|
||||
sampleRate: Int(asbd.pointee.mSampleRate)
|
||||
)
|
||||
if self.audioConverter?.format != format {
|
||||
self.audioConverter = CustomAudioConverter(asbd: asbd)
|
||||
}
|
||||
if let audioConverter = self.audioConverter {
|
||||
if let data = audioConverter.convert(sampleBuffer: sampleBuffer), !data.isEmpty {
|
||||
self.screencastBufferClientContext?.writeAudioData(data: data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class CustomAudioConverter {
|
||||
struct Format: Equatable {
|
||||
let numChannels: Int
|
||||
let sampleRate: Int
|
||||
}
|
||||
|
||||
let format: Format
|
||||
|
||||
var currentInputDescription: UnsafePointer<AudioStreamBasicDescription>?
|
||||
var currentBuffer: AudioBuffer?
|
||||
var currentBufferOffset: UInt32 = 0
|
||||
|
||||
init(asbd: UnsafePointer<AudioStreamBasicDescription>) {
|
||||
self.format = Format(
|
||||
numChannels: Int(asbd.pointee.mChannelsPerFrame),
|
||||
sampleRate: Int(asbd.pointee.mSampleRate)
|
||||
)
|
||||
}
|
||||
|
||||
func convert(sampleBuffer: CMSampleBuffer) -> Data? {
|
||||
guard let formatDescription = CMSampleBufferGetFormatDescription(sampleBuffer) else {
|
||||
return nil
|
||||
}
|
||||
guard let asbd = CMAudioFormatDescriptionGetStreamBasicDescription(formatDescription) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var bufferList = AudioBufferList()
|
||||
var blockBuffer: CMBlockBuffer? = nil
|
||||
CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(
|
||||
sampleBuffer,
|
||||
bufferListSizeNeededOut: nil,
|
||||
bufferListOut: &bufferList,
|
||||
bufferListSize: MemoryLayout<AudioBufferList>.size,
|
||||
blockBufferAllocator: nil,
|
||||
blockBufferMemoryAllocator: nil,
|
||||
flags: kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment,
|
||||
blockBufferOut: &blockBuffer
|
||||
)
|
||||
let size = bufferList.mBuffers.mDataByteSize
|
||||
guard size != 0, let mData = bufferList.mBuffers.mData else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var outputDescription = AudioStreamBasicDescription(
|
||||
mSampleRate: 48000.0,
|
||||
mFormatID: kAudioFormatLinearPCM,
|
||||
mFormatFlags: kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked,
|
||||
mBytesPerPacket: 2,
|
||||
mFramesPerPacket: 1,
|
||||
mBytesPerFrame: 2,
|
||||
mChannelsPerFrame: 1,
|
||||
mBitsPerChannel: 16,
|
||||
mReserved: 0
|
||||
)
|
||||
var maybeAudioConverter: AudioConverterRef?
|
||||
let _ = AudioConverterNew(asbd, &outputDescription, &maybeAudioConverter)
|
||||
guard let audioConverter = maybeAudioConverter else {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.currentBuffer = AudioBuffer(
|
||||
mNumberChannels: asbd.pointee.mChannelsPerFrame,
|
||||
mDataByteSize: UInt32(size),
|
||||
mData: mData
|
||||
)
|
||||
self.currentBufferOffset = 0
|
||||
self.currentInputDescription = asbd
|
||||
|
||||
var numPackets: UInt32?
|
||||
let outputSize = 32768 * 2
|
||||
var outputBuffer = Data(count: outputSize)
|
||||
outputBuffer.withUnsafeMutableBytes { (outputBytes: UnsafeMutableRawBufferPointer) -> Void in
|
||||
var outputBufferList = AudioBufferList()
|
||||
outputBufferList.mNumberBuffers = 1
|
||||
outputBufferList.mBuffers.mNumberChannels = outputDescription.mChannelsPerFrame
|
||||
outputBufferList.mBuffers.mDataByteSize = UInt32(outputSize)
|
||||
outputBufferList.mBuffers.mData = outputBytes.baseAddress!
|
||||
|
||||
var outputDataPacketSize = UInt32(outputSize) / outputDescription.mBytesPerPacket
|
||||
|
||||
let result = AudioConverterFillComplexBuffer(
|
||||
audioConverter,
|
||||
converterComplexInputDataProc,
|
||||
Unmanaged.passUnretained(self).toOpaque(),
|
||||
&outputDataPacketSize,
|
||||
&outputBufferList,
|
||||
nil
|
||||
)
|
||||
if result == noErr {
|
||||
numPackets = outputDataPacketSize
|
||||
}
|
||||
}
|
||||
|
||||
AudioConverterDispose(audioConverter)
|
||||
|
||||
if let numPackets = numPackets {
|
||||
outputBuffer.count = Int(numPackets * outputDescription.mBytesPerPacket)
|
||||
return outputBuffer
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func converterComplexInputDataProc(inAudioConverter: AudioConverterRef, ioNumberDataPackets: UnsafeMutablePointer<UInt32>, ioData: UnsafeMutablePointer<AudioBufferList>, ioDataPacketDescription: UnsafeMutablePointer<UnsafeMutablePointer<AudioStreamPacketDescription>?>?, inUserData: UnsafeMutableRawPointer?) -> Int32 {
|
||||
guard let inUserData = inUserData else {
|
||||
ioNumberDataPackets.pointee = 0
|
||||
return 0
|
||||
}
|
||||
let instance = Unmanaged<CustomAudioConverter>.fromOpaque(inUserData).takeUnretainedValue()
|
||||
guard let currentBuffer = instance.currentBuffer else {
|
||||
ioNumberDataPackets.pointee = 0
|
||||
return 0
|
||||
}
|
||||
guard let currentInputDescription = instance.currentInputDescription else {
|
||||
ioNumberDataPackets.pointee = 0
|
||||
return 0
|
||||
}
|
||||
|
||||
let numPacketsInBuffer = currentBuffer.mDataByteSize / currentInputDescription.pointee.mBytesPerPacket
|
||||
let numPacketsAvailable = numPacketsInBuffer - instance.currentBufferOffset / currentInputDescription.pointee.mBytesPerPacket
|
||||
|
||||
let numPacketsToRead = min(ioNumberDataPackets.pointee, numPacketsAvailable)
|
||||
ioNumberDataPackets.pointee = numPacketsToRead
|
||||
|
||||
ioData.pointee.mNumberBuffers = 1
|
||||
ioData.pointee.mBuffers.mData = currentBuffer.mData?.advanced(by: Int(instance.currentBufferOffset))
|
||||
ioData.pointee.mBuffers.mDataByteSize = currentBuffer.mDataByteSize - instance.currentBufferOffset
|
||||
ioData.pointee.mBuffers.mNumberChannels = currentBuffer.mNumberChannels
|
||||
|
||||
instance.currentBufferOffset += numPacketsToRead * currentInputDescription.pointee.mBytesPerPacket
|
||||
|
||||
return 0
|
||||
}
|
@ -38,7 +38,7 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi
|
||||
|
||||
let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown"
|
||||
|
||||
self.impl = NotificationViewControllerImpl(initializationData: NotificationViewControllerInitializationData(appGroupPath: appGroupUrl.path, apiId: buildConfig.apiId, apiHash: buildConfig.apiHash, languagesCategory: languagesCategory, encryptionParameters: encryptionParameters, appVersion: appVersion, bundleData: buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), setPreferredContentSize: { [weak self] size in
|
||||
self.impl = NotificationViewControllerImpl(initializationData: NotificationViewControllerInitializationData(appBundleId: baseAppBundleId, appGroupPath: appGroupUrl.path, apiId: buildConfig.apiId, apiHash: buildConfig.apiHash, languagesCategory: languagesCategory, encryptionParameters: encryptionParameters, appVersion: appVersion, bundleData: buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), setPreferredContentSize: { [weak self] size in
|
||||
self?.preferredContentSize = size
|
||||
})
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
@implementation Serialization
|
||||
|
||||
- (NSUInteger)currentLayer {
|
||||
return 125;
|
||||
return 131;
|
||||
}
|
||||
|
||||
- (id _Nullable)parseMessage:(NSData * _Nullable)data {
|
||||
|
@ -45,7 +45,7 @@ class ShareRootController: UIViewController {
|
||||
|
||||
let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown"
|
||||
|
||||
self.impl = ShareRootControllerImpl(initializationData: ShareRootControllerInitializationData(appGroupPath: appGroupUrl.path, apiId: buildConfig.apiId, apiHash: buildConfig.apiHash, languagesCategory: languagesCategory, encryptionParameters: encryptionParameters, appVersion: appVersion, bundleData: buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), getExtensionContext: { [weak self] in
|
||||
self.impl = ShareRootControllerImpl(initializationData: ShareRootControllerInitializationData(appBundleId: baseAppBundleId, appGroupPath: appGroupUrl.path, apiId: buildConfig.apiId, apiHash: buildConfig.apiHash, languagesCategory: languagesCategory, encryptionParameters: encryptionParameters, appVersion: appVersion, bundleData: buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), getExtensionContext: { [weak self] in
|
||||
return self?.extensionContext
|
||||
})
|
||||
}
|
||||
|
@ -1,46 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>${APP_NAME}</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(PRODUCT_BUNDLE_SHORT_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${BUILD_NUMBER}</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionAttributes</key>
|
||||
<dict>
|
||||
<key>IntentsRestrictedWhileLocked</key>
|
||||
<array/>
|
||||
<key>IntentsRestrictedWhileProtectedDataUnavailable</key>
|
||||
<array/>
|
||||
<key>IntentsSupported</key>
|
||||
<array>
|
||||
<string>INSendMessageIntent</string>
|
||||
<string>INStartAudioCallIntent</string>
|
||||
<string>INSearchForMessagesIntent</string>
|
||||
<string>INSetMessageAttributeIntent</string>
|
||||
<string>INSearchCallHistoryIntent</string>
|
||||
</array>
|
||||
</dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.intents-service</string>
|
||||
<key>NSExtensionPrincipalClass</key>
|
||||
<string>IntentHandler</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
@ -35,7 +35,7 @@ private func parseAppSpecificContactReference(_ value: String) -> PeerId? {
|
||||
}
|
||||
let idString = String(value[value.index(value.startIndex, offsetBy: phonebookUsernamePrefix.count)...])
|
||||
if let id = Int32(idString) {
|
||||
return PeerId(namespace: Namespaces.Peer.CloudUser, id: id)
|
||||
return PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt32Value(id))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -114,7 +114,7 @@ class DefaultIntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo
|
||||
|
||||
self.rootPath = rootPath
|
||||
|
||||
TempBox.initializeShared(basePath: rootPath, processType: "siri", launchSpecificId: arc4random64())
|
||||
TempBox.initializeShared(basePath: rootPath, processType: "siri", launchSpecificId: Int64.random(in: Int64.min ... Int64.max))
|
||||
|
||||
let logsPath = rootPath + "/siri-logs"
|
||||
let _ = try? FileManager.default.createDirectory(atPath: logsPath, withIntermediateDirectories: true, attributes: nil)
|
||||
@ -619,7 +619,7 @@ class DefaultIntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo
|
||||
}
|
||||
|
||||
for (_, messageId) in maxMessageIdsToApply {
|
||||
signals.append(applyMaxReadIndexInteractively(postbox: account.postbox, stateManager: account.stateManager, index: MessageIndex(id: messageId, timestamp: 0))
|
||||
signals.append(TelegramEngine(account: account).messages.applyMaxReadIndexInteractively(index: MessageIndex(id: messageId, timestamp: 0))
|
||||
|> castError(IntentHandlingError.self))
|
||||
}
|
||||
|
||||
@ -793,7 +793,7 @@ class DefaultIntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo
|
||||
var accountResults: [Signal<INObjectSection<Friend>, Error>] = []
|
||||
|
||||
for (accountId, accountPeerId, _) in accounts {
|
||||
accountResults.append(accountTransaction(rootPath: rootPath, id: accountId, encryptionParameters: encryptionParameters, isReadOnly: true, useCopy: true, transaction: { postbox, transaction -> INObjectSection<Friend> in
|
||||
accountResults.append(accountTransaction(rootPath: rootPath, id: accountId, encryptionParameters: encryptionParameters, isReadOnly: true, useCopy: false, transaction: { postbox, transaction -> INObjectSection<Friend> in
|
||||
var accountTitle: String = ""
|
||||
if let peer = transaction.getPeer(accountPeerId) as? TelegramUser {
|
||||
if let username = peer.username, !username.isEmpty {
|
||||
@ -884,7 +884,7 @@ private final class WidgetIntentHandler {
|
||||
|
||||
self.rootPath = rootPath
|
||||
|
||||
TempBox.initializeShared(basePath: rootPath, processType: "siri", launchSpecificId: arc4random64())
|
||||
TempBox.initializeShared(basePath: rootPath, processType: "siri", launchSpecificId: Int64.random(in: Int64.min ... Int64.max))
|
||||
|
||||
let logsPath = rootPath + "/siri-logs"
|
||||
let _ = try? FileManager.default.createDirectory(atPath: logsPath, withIntermediateDirectories: true, attributes: nil)
|
||||
@ -962,7 +962,7 @@ private final class WidgetIntentHandler {
|
||||
var accountResults: [Signal<INObjectSection<Friend>, Error>] = []
|
||||
|
||||
for (accountId, accountPeerId, _) in accounts {
|
||||
accountResults.append(accountTransaction(rootPath: rootPath, id: accountId, encryptionParameters: encryptionParameters, isReadOnly: true, useCopy: true, transaction: { postbox, transaction -> INObjectSection<Friend> in
|
||||
accountResults.append(accountTransaction(rootPath: rootPath, id: accountId, encryptionParameters: encryptionParameters, isReadOnly: true, useCopy: false, transaction: { postbox, transaction -> INObjectSection<Friend> in
|
||||
var accountTitle: String = ""
|
||||
if let peer = transaction.getPeer(accountPeerId) as? TelegramUser {
|
||||
if let username = peer.username, !username.isEmpty {
|
||||
@ -1045,10 +1045,10 @@ private final class WidgetIntentHandler {
|
||||
if !isActive {
|
||||
continue
|
||||
}
|
||||
accountResults.append(accountTransaction(rootPath: rootPath, id: accountId, encryptionParameters: encryptionParameters, isReadOnly: true, useCopy: true, transaction: { postbox, transaction -> [Friend] in
|
||||
accountResults.append(accountTransaction(rootPath: rootPath, id: accountId, encryptionParameters: encryptionParameters, isReadOnly: true, useCopy: false, transaction: { postbox, transaction -> [Friend] in
|
||||
var peers: [Peer] = []
|
||||
|
||||
for id in getRecentPeers(transaction: transaction) {
|
||||
for id in _internal_getRecentPeers(transaction: transaction) {
|
||||
if let peer = transaction.getPeer(id), !(peer is TelegramSecretChat), !peer.isDeleted {
|
||||
peers.append(peer)
|
||||
}
|
||||
|
@ -166,7 +166,7 @@ private func callWithTelegramMessage(_ telegramMessage: Message, account: Accoun
|
||||
|
||||
@available(iOSApplicationExtension 10.0, iOS 10.0, *)
|
||||
private func messageWithTelegramMessage(_ telegramMessage: Message) -> INMessage? {
|
||||
guard let author = telegramMessage.author, let user = telegramMessage.peers[author.id] as? TelegramUser, user.id.id != 777000 else {
|
||||
guard let author = telegramMessage.author, let user = telegramMessage.peers[author.id] as? TelegramUser, user.id.id._internalGetInt32Value() != 777000 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -72,4 +72,44 @@
|
||||
<key>UIPrerenderedIcon</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>New1</key>
|
||||
<dict>
|
||||
<key>CFBundleIconFiles</key>
|
||||
<array>
|
||||
<string>New1_20x20</string>
|
||||
<string>New1_29x29</string>
|
||||
<string>New1_40x40</string>
|
||||
<string>New1_58x58</string>
|
||||
<string>New1_60x60</string>
|
||||
<string>New1_76x76</string>
|
||||
<string>New1_80x80</string>
|
||||
<string>New1_87x87</string>
|
||||
<string>New1_120x120</string>
|
||||
<string>New1_152x152</string>
|
||||
<string>New1_167x167</string>
|
||||
<string>New1_180x180</string>
|
||||
</array>
|
||||
<key>UIPrerenderedIcon</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>New2</key>
|
||||
<dict>
|
||||
<key>CFBundleIconFiles</key>
|
||||
<array>
|
||||
<string>New2_20x20</string>
|
||||
<string>New2_29x29</string>
|
||||
<string>New2_40x40</string>
|
||||
<string>New2_58x58</string>
|
||||
<string>New2_60x60</string>
|
||||
<string>New2_76x76</string>
|
||||
<string>New2_80x80</string>
|
||||
<string>New2_87x87</string>
|
||||
<string>New2_120x120</string>
|
||||
<string>New2_152x152</string>
|
||||
<string>New2_167x167</string>
|
||||
<string>New2_180x180</string>
|
||||
</array>
|
||||
<key>UIPrerenderedIcon</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
@ -66,4 +66,44 @@
|
||||
<key>UIPrerenderedIcon</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>New1</key>
|
||||
<dict>
|
||||
<key>CFBundleIconFiles</key>
|
||||
<array>
|
||||
<string>New1_20x20</string>
|
||||
<string>New1_29x29</string>
|
||||
<string>New1_40x40</string>
|
||||
<string>New1_58x58</string>
|
||||
<string>New1_60x60</string>
|
||||
<string>New1_76x76</string>
|
||||
<string>New1_80x80</string>
|
||||
<string>New1_87x87</string>
|
||||
<string>New1_120x120</string>
|
||||
<string>New1_152x152</string>
|
||||
<string>New1_167x167</string>
|
||||
<string>New1_180x180</string>
|
||||
</array>
|
||||
<key>UIPrerenderedIcon</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>New2</key>
|
||||
<dict>
|
||||
<key>CFBundleIconFiles</key>
|
||||
<array>
|
||||
<string>New2_20x20</string>
|
||||
<string>New2_29x29</string>
|
||||
<string>New2_40x40</string>
|
||||
<string>New2_58x58</string>
|
||||
<string>New2_60x60</string>
|
||||
<string>New2_76x76</string>
|
||||
<string>New2_80x80</string>
|
||||
<string>New2_87x87</string>
|
||||
<string>New2_120x120</string>
|
||||
<string>New2_152x152</string>
|
||||
<string>New2_167x167</string>
|
||||
<string>New2_180x180</string>
|
||||
</array>
|
||||
<key>UIPrerenderedIcon</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
BIN
Telegram/Telegram-iOS/New1_120x120.png
Normal file
After Width: | Height: | Size: 8.5 KiB |
BIN
Telegram/Telegram-iOS/New1_152x152.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
Telegram/Telegram-iOS/New1_167x167.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
Telegram/Telegram-iOS/New1_180x180.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
Telegram/Telegram-iOS/New1_20x20.png
Normal file
After Width: | Height: | Size: 889 B |
BIN
Telegram/Telegram-iOS/New1_29x29.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
Telegram/Telegram-iOS/New1_40x40.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
Telegram/Telegram-iOS/New1_58x58.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
Telegram/Telegram-iOS/New1_60x60.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
Telegram/Telegram-iOS/New1_76x76.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
Telegram/Telegram-iOS/New1_80x80.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
Telegram/Telegram-iOS/New1_87x87.png
Normal file
After Width: | Height: | Size: 5.5 KiB |
BIN
Telegram/Telegram-iOS/New2_120x120.png
Normal file
After Width: | Height: | Size: 8.9 KiB |
BIN
Telegram/Telegram-iOS/New2_152x152.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
Telegram/Telegram-iOS/New2_167x167.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
Telegram/Telegram-iOS/New2_180x180.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
Telegram/Telegram-iOS/New2_20x20.png
Normal file
After Width: | Height: | Size: 917 B |
BIN
Telegram/Telegram-iOS/New2_29x29.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
Telegram/Telegram-iOS/New2_40x40.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
Telegram/Telegram-iOS/New2_58x58.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
Telegram/Telegram-iOS/New2_60x60.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
Telegram/Telegram-iOS/New2_76x76.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
Telegram/Telegram-iOS/New2_80x80.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
Telegram/Telegram-iOS/New2_87x87.png
Normal file
After Width: | Height: | Size: 5.8 KiB |
BIN
Telegram/Telegram-iOS/Resources/VoiceCancelReminder.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/VoiceCancelReminderToMute.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/VoiceHand_10.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/VoiceHand_8.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/VoiceHand_9.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/VoiceMuteToRaiseHand.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/VoiceRaiseHandToMute.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/VoiceSetReminder.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/VoiceSetReminderToMute.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/VoiceSetReminderToRaiseHand.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/VoiceStart.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/VoiceUnmuteToRaiseHand.tgs
Normal file
BIN
Telegram/Telegram-iOS/Resources/begin_record.mp3
Normal file
BIN
Telegram/Telegram-iOS/Resources/voip_busy.mp3
Normal file
BIN
Telegram/Telegram-iOS/Resources/voip_end.mp3
Normal file
BIN
Telegram/Telegram-iOS/Resources/voip_fail.mp3
Normal file
BIN
Telegram/Telegram-iOS/Resources/voip_ringback.mp3
Normal file
@ -104,9 +104,8 @@
|
||||
"PUSH_MESSAGES_1" = "%1$@|sent you a message";
|
||||
"PUSH_MESSAGES_any" = "%1$@|sent you %2$d messages";
|
||||
"PUSH_ALBUM" = "%1$@|sent you an album";
|
||||
"PUSH_MESSAGE_DOCS" = "%1$@|sent you %2$d files";
|
||||
"PUSH_MESSAGE_DOCS_1" = "%1$@|sent you a file";
|
||||
"PUSH_MESSAGE_DOCS_any" = "%1$@|sent you %2$d files";
|
||||
"PUSH_MESSAGE_FILES_1" = "%1$@|sent you a file";
|
||||
"PUSH_MESSAGE_FILES_any" = "%1$@|sent you %2$d files";
|
||||
|
||||
|
||||
"PUSH_CHANNEL_MESSAGE_TEXT" = "%1$@|%2$@";
|
||||
@ -2552,7 +2551,6 @@ Unused sets are archived when you add more.";
|
||||
"Message.ForwardedMessageShort" = "Forwarded From\n%@";
|
||||
|
||||
"Checkout.LiabilityAlertTitle" = "Warning";
|
||||
"Checkout.LiabilityAlert" = "Neither Telegram, nor %1$@ will have access to your credit card information. Credit card details will be handled only by the payment system, %2$@.\n\nPayments will go directly to the developer of %1$@. Telegram cannot provide any guarantees, so proceed at your own risk. In case of problems, please contact the developer of %1$@ or your bank.";
|
||||
|
||||
"Settings.AppLanguage" = "Language";
|
||||
"Settings.AppLanguage.Unofficial" = "UNOFFICIAL";
|
||||
@ -3950,6 +3948,7 @@ Unused sets are archived when you add more.";
|
||||
"WallpaperPreview.Title" = "Background Preview";
|
||||
"WallpaperPreview.PreviewTopText" = "Press Set to apply the background";
|
||||
"WallpaperPreview.PreviewBottomText" = "Enjoy the view";
|
||||
|
||||
"WallpaperPreview.SwipeTopText" = "Swipe left or right to preview more backgrounds";
|
||||
"WallpaperPreview.SwipeBottomText" = "Backgrounds for the god of backgrounds!";
|
||||
"WallpaperPreview.SwipeColorsTopText" = "Swipe left or right to see more colors";
|
||||
@ -4430,6 +4429,8 @@ Sorry for the inconvenience.";
|
||||
"Appearance.AppIconClassicX" = "Classic X";
|
||||
"Appearance.AppIconFilled" = "Filled";
|
||||
"Appearance.AppIconFilledX" = "Filled X";
|
||||
"Appearance.AppIconNew1" = "Sunset";
|
||||
"Appearance.AppIconNew2" = "Aqua";
|
||||
|
||||
"Appearance.ThemeCarouselClassic" = "Classic";
|
||||
"Appearance.ThemeCarouselDay" = "Day";
|
||||
@ -5742,6 +5743,7 @@ Sorry for the inconvenience.";
|
||||
|
||||
"Notification.VoiceChatStarted" = "%1$@ started a voice chat";
|
||||
"Notification.VoiceChatEnded" = "Voice chat ended (%@)";
|
||||
"Notification.VoiceChatEndedGroup" = "%1$@ ended the voice chat (%2$@)";
|
||||
|
||||
"VoiceChat.Panel.TapToJoin" = "Tap to join";
|
||||
"VoiceChat.Panel.Members_0" = "%@ participants";
|
||||
@ -5774,10 +5776,13 @@ Sorry for the inconvenience.";
|
||||
|
||||
"VoiceChat.CreateNewVoiceChatText" = "Voice chat ended. Start a new one?";
|
||||
"VoiceChat.CreateNewVoiceChatStart" = "Start";
|
||||
"VoiceChat.CreateNewVoiceChatStartNow" = "Start Now";
|
||||
"VoiceChat.CreateNewVoiceChatSchedule" = "Schedule";
|
||||
|
||||
"PUSH_CHAT_VOICECHAT_START" = "%2$@|%1$@ started a voice chat";
|
||||
"PUSH_CHAT_VOICECHAT_INVITE" = "%2$@|%1$@ invited %3$@ to the voice chat";
|
||||
"PUSH_CHAT_VOICECHAT_INVITE_YOU" = "%2$|@%1$@ invited you to the voice chat";
|
||||
"PUSH_CHAT_VOICECHAT_INVITE_YOU" = "%2$@|%1$@ invited you to the voice chat";
|
||||
"PUSH_CHAT_VOICECHAT_END" = "%2$@|%1$@ has ended the voice chat";
|
||||
|
||||
"Call.VoiceChatInProgressTitle" = "Voice Chat in Progress";
|
||||
"Call.VoiceChatInProgressMessageCall" = "Leave voice chat in %1$@ and start a call with %2$@?";
|
||||
@ -6037,7 +6042,6 @@ Sorry for the inconvenience.";
|
||||
"Conversation.ForwardTooltip.TwoChats.Many" = "Messages forwarded to **%@** and **%@**";
|
||||
"Conversation.ForwardTooltip.ManyChats.One" = "Message forwarded to **%@** and %@ others";
|
||||
"Conversation.ForwardTooltip.ManyChats.Many" = "Messages forwarded to **%@** and %@ others";
|
||||
|
||||
"Conversation.ForwardTooltip.SavedMessages.One" = "Message forwarded to **Saved Messages**";
|
||||
"Conversation.ForwardTooltip.SavedMessages.Many" = "Messages forwarded to **Saved Messages**";
|
||||
|
||||
@ -6255,6 +6259,7 @@ Sorry for the inconvenience.";
|
||||
|
||||
"VoiceChat.YouCanNowSpeak" = "You can now speak";
|
||||
"VoiceChat.YouCanNowSpeakIn" = "You can now speak in **%@**";
|
||||
"VoiceChat.UserCanNowSpeak" = "**%@** can now speak";
|
||||
|
||||
"VoiceChat.MutedByAdmin" = "Muted by Admin";
|
||||
"VoiceChat.MutedByAdminHelp" = "Tap if you want to speak";
|
||||
@ -6292,7 +6297,277 @@ Sorry for the inconvenience.";
|
||||
"VoiceChat.LeaveConfirmation" = "Are you sure you want to leave this voice chat?";
|
||||
"VoiceChat.LeaveVoiceChat" = "Leave Voice Chat";
|
||||
"VoiceChat.LeaveAndEndVoiceChat" = "End Voice Chat";
|
||||
"VoiceChat.LeaveAndCancelVoiceChat" = "Abort Voice Chat";
|
||||
|
||||
"VoiceChat.ForwardTooltip.Chat" = "Invite link forwarded to **%@**";
|
||||
"VoiceChat.ForwardTooltip.TwoChats" = "Invite link forwarded to **%@** and **%@**";
|
||||
"VoiceChat.ForwardTooltip.ManyChats" = "Invite link forwarded to **%@** and %@ others";
|
||||
|
||||
"GroupRemoved.ViewChannelInfo" = "View Channel";
|
||||
|
||||
"UserInfo.ContactForwardTooltip.Chat.One" = "Contact forwarded to **%@**";
|
||||
"UserInfo.ContactForwardTooltip.TwoChats.One" = "Contact forwarded to **%@** and **%@**";
|
||||
"UserInfo.ContactForwardTooltip.ManyChats.One" = "Contact forwarded to **%@** and %@ others";
|
||||
"UserInfo.ContactForwardTooltip.SavedMessages.One" = "Contact forwarded to **Saved Messages**";
|
||||
|
||||
"UserInfo.LinkForwardTooltip.Chat.One" = "Link forwarded to **%@**";
|
||||
"UserInfo.LinkForwardTooltip.TwoChats.One" = "Link forwarded to **%@** and **%@**";
|
||||
"UserInfo.LinkForwardTooltip.ManyChats.One" = "Link forwarded to **%@** and %@ others";
|
||||
"UserInfo.LinkForwardTooltip.SavedMessages.One" = "Link forwarded to **Saved Messages**";
|
||||
|
||||
"VoiceChat.You" = "this is you";
|
||||
"VoiceChat.ChangePhoto" = "Change Photo";
|
||||
"VoiceChat.EditBio" = "Edit Bio";
|
||||
"VoiceChat.EditBioTitle" = "Bio";
|
||||
"VoiceChat.EditBioText" = "Any details such as age, occupation or city.";
|
||||
"VoiceChat.EditBioPlaceholder" = "Bio";
|
||||
"VoiceChat.EditBioSave" = "Save";
|
||||
"VoiceChat.EditBioSuccess" = "Your bio is changed.";
|
||||
|
||||
"VoiceChat.EditDescription" = "Edit Description";
|
||||
"VoiceChat.EditDescriptionTitle" = "Description";
|
||||
"VoiceChat.EditDescriptionText" = "Any details such as age, occupation or city.";
|
||||
"VoiceChat.EditDescriptionPlaceholder" = "Description";
|
||||
"VoiceChat.EditDescriptionSave" = "Save";
|
||||
"VoiceChat.EditDescriptionSuccess" = "Description is changed.";
|
||||
|
||||
"VoiceChat.SendPublicLinkText" = "%1$@ isn't a member of \"%2$@\" yet. Send them a public invite link instead?";
|
||||
"VoiceChat.SendPublicLinkSend" = "Send";
|
||||
|
||||
"VoiceChat.TapToAddPhotoOrBio" = "tap to add photo or bio";
|
||||
"VoiceChat.TapToAddPhoto" = "tap to add photo";
|
||||
"VoiceChat.TapToAddBio" = "tap to add bio";
|
||||
"VoiceChat.ImproveYourProfileText" = "You can improve your profile by adding missing information.";
|
||||
|
||||
"VoiceChat.AddPhoto" = "Add Photo";
|
||||
"VoiceChat.AddBio" = "Add Bio";
|
||||
"VoiceChat.ChangeName" = "Change Name";
|
||||
"VoiceChat.ChangeNameTitle" = "Change Name";
|
||||
"VoiceChat.EditNameSuccess" = "Your name is changed.";
|
||||
|
||||
"VoiceChat.Video" = "video";
|
||||
|
||||
"VoiceChat.PinVideo" = "Pin Video";
|
||||
"VoiceChat.UnpinVideo" = "Unpin Video";
|
||||
|
||||
"Notification.VoiceChatScheduledChannel" = "Voice chat scheduled for %@";
|
||||
"Notification.VoiceChatScheduled" = "%1$@ scheduled a voice chat for %2$@";
|
||||
|
||||
"Notification.VoiceChatScheduledTodayChannel" = "Voice chat scheduled for today at %@";
|
||||
"Notification.VoiceChatScheduledToday" = "%1$@ scheduled a voice chat for today at %2$@";
|
||||
|
||||
"Notification.VoiceChatScheduledTomorrowChannel" = "Voice chat scheduled for tomorrow at %@";
|
||||
"Notification.VoiceChatScheduledTomorrow" = "%1$@ scheduled a voice chat for tomorrow at %2$@";
|
||||
|
||||
"VoiceChat.StartsIn" = "Starts in";
|
||||
"VoiceChat.LateBy" = "Late by";
|
||||
|
||||
"VoiceChat.StatusStartsIn" = "starts in %@";
|
||||
"VoiceChat.StatusLateBy" = "late by %@";
|
||||
|
||||
"VoiceChat.Scheduled" = "Scheduled";
|
||||
|
||||
"VoiceChat.StartNow" = "Start Now";
|
||||
"VoiceChat.SetReminder" = "Set Reminder";
|
||||
"VoiceChat.CancelReminder" = "Cancel Reminder";
|
||||
|
||||
"VoiceChat.ShareShort" = "share";
|
||||
"VoiceChat.TapToEditTitle" = "Tap to edit title";
|
||||
|
||||
"ChannelInfo.ScheduleVoiceChat" = "Schedule Voice Chat";
|
||||
|
||||
"ScheduleVoiceChat.Title" = "Schedule Voice Chat";
|
||||
"ScheduleVoiceChat.GroupText" = "The members of the group will be notified that the voice chat will start in %@.";
|
||||
"ScheduleVoiceChat.ChannelText" = "The members of the channel will be notified that the voice chat will start in %@.";
|
||||
|
||||
"ScheduleVoiceChat.ScheduleToday" = "Start today at %@";
|
||||
"ScheduleVoiceChat.ScheduleTomorrow" = "Start tomorrow at %@";
|
||||
"ScheduleVoiceChat.ScheduleOn" = "Start on %@ at %@";
|
||||
|
||||
"Conversation.ScheduledVoiceChat" = "Scheduled Voice Chat";
|
||||
|
||||
"Conversation.ScheduledVoiceChatStartsOn" = "Voice chat starts on %@";
|
||||
"Conversation.ScheduledVoiceChatStartsOnShort" = "Starts on %@";
|
||||
"Conversation.ScheduledVoiceChatStartsToday" = "Voice chat starts today at %@";
|
||||
"Conversation.ScheduledVoiceChatStartsTodayShort" = "Starts today at %@";
|
||||
"Conversation.ScheduledVoiceChatStartsTomorrow" = "Voice chat starts tomorrow at %@";
|
||||
"Conversation.ScheduledVoiceChatStartsTomorrowShort" = "Starts tomorrow at %@";
|
||||
|
||||
"VoiceChat.CancelVoiceChat" = "Abort Voice Chat";
|
||||
"VoiceChat.CancelConfirmationTitle" = "Abort Voice Chat";
|
||||
"VoiceChat.CancelConfirmationText" = "Do you want to abort the scheduled voice chat?";
|
||||
"VoiceChat.CancelConfirmationEnd" = "Abort";
|
||||
|
||||
"ScheduledIn.Seconds_1" = "%@ second";
|
||||
"ScheduledIn.Seconds_2" = "%@ seconds";
|
||||
"ScheduledIn.Seconds_3_10" = "%@ seconds";
|
||||
"ScheduledIn.Seconds_any" = "%@ seconds";
|
||||
"ScheduledIn.Seconds_many" = "%@ seconds";
|
||||
"ScheduledIn.Seconds_0" = "%@ seconds";
|
||||
"ScheduledIn.Minutes_1" = "%@ minute";
|
||||
"ScheduledIn.Minutes_2" = "%@ minutes";
|
||||
"ScheduledIn.Minutes_3_10" = "%@ minutes";
|
||||
"ScheduledIn.Minutes_any" = "%@ minutes";
|
||||
"ScheduledIn.Minutes_many" = "%@ minutes";
|
||||
"ScheduledIn.Minutes_0" = "%@ minutes";
|
||||
"ScheduledIn.Hours_1" = "%@ hour";
|
||||
"ScheduledIn.Hours_2" = "%@ hours";
|
||||
"ScheduledIn.Hours_3_10" = "%@ hours";
|
||||
"ScheduledIn.Hours_any" = "%@ hours";
|
||||
"ScheduledIn.Hours_many" = "%@ hours";
|
||||
"ScheduledIn.Hours_0" = "%@ hours";
|
||||
"ScheduledIn.Days_1" = "%@ day";
|
||||
"ScheduledIn.Days_2" = "%@ days";
|
||||
"ScheduledIn.Days_3_10" = "%@ days";
|
||||
"ScheduledIn.Days_any" = "%@ days";
|
||||
"ScheduledIn.Days_many" = "%@ days";
|
||||
"ScheduledIn.Days_0" = "%@ days";
|
||||
"ScheduledIn.Weeks_1" = "%@ week";
|
||||
"ScheduledIn.Weeks_2" = "%@ weeks";
|
||||
"ScheduledIn.Weeks_3_10" = "%@ weeks";
|
||||
"ScheduledIn.Weeks_any" = "%@ weeks";
|
||||
"ScheduledIn.Weeks_many" = "%@ weeks";
|
||||
"ScheduledIn.Weeks_0" = "%@ weeks";
|
||||
"ScheduledIn.Months_1" = "%@ month";
|
||||
"ScheduledIn.Months_2" = "%@ months";
|
||||
"ScheduledIn.Months_3_10" = "%@ months";
|
||||
"ScheduledIn.Months_any" = "%@ months";
|
||||
"ScheduledIn.Months_many" = "%@ months";
|
||||
"ScheduledIn.Months_0" = "%@ months";
|
||||
"ScheduledIn.Years_1" = "%@ year";
|
||||
"ScheduledIn.Years_2" = "%@ years";
|
||||
"ScheduledIn.Years_3_10" = "%@ years";
|
||||
"ScheduledIn.Years_any" = "%@ years";
|
||||
"ScheduledIn.Months_many" = "%@ years";
|
||||
|
||||
"Checkout.PaymentLiabilityAlert" = "Neither Telegram, nor {target} will have access to your credit card information. Credit card details will be handled only by the payment system, {payment_system}.\n\nPayments will go directly to the developer of {target}. Telegram cannot provide any guarantees, so proceed at your own risk. In case of problems, please contact the developer of {target} or your bank.";
|
||||
|
||||
"Checkout.OptionalTipItem" = "Tip (Optional)";
|
||||
"Checkout.TipItem" = "Tip";
|
||||
"Checkout.OptionalTipItemPlaceholder" = "Enter Custom";
|
||||
|
||||
"VoiceChat.ReminderNotify" = "We will notify you when it starts.";
|
||||
|
||||
"Checkout.SuccessfulTooltip" = "You paid %1$@ for %2$@.";
|
||||
|
||||
"Privacy.ContactsReset.ContactsDeleted" = "All synced contacts deleted.";
|
||||
|
||||
"Privacy.DeleteDrafts.DraftsDeleted" = "All cloud drafts deleted.";
|
||||
|
||||
"Privacy.PaymentsClear.PaymentInfoCleared" = "Payment info cleared.";
|
||||
"Privacy.PaymentsClear.ShippingInfoCleared" = "Shipping info cleared.";
|
||||
"Privacy.PaymentsClear.AllInfoCleared" = "Payment and shipping info cleared.";
|
||||
|
||||
"Settings.Tips" = "Telegram Features";
|
||||
"Settings.TipsUsername" = "TelegramTips";
|
||||
|
||||
"Calls.NoVoiceAndVideoCallsPlaceholder" = "Your recent voice and video calls will appear here.";
|
||||
"Calls.StartNewCall" = "Start New Call";
|
||||
|
||||
"VoiceChat.VideoPreviewTitle" = "Video Preview";
|
||||
"VoiceChat.VideoPreviewDescription" = "Are you sure you want to share your video?";
|
||||
"VoiceChat.VideoPreviewShareCamera" = "Share Camera Video";
|
||||
"VoiceChat.VideoPreviewShareScreen" = "Share Screen";
|
||||
"VoiceChat.VideoPreviewStopScreenSharing" = "Stop Screen Sharing";
|
||||
|
||||
"VoiceChat.TapToViewCameraVideo" = "Tap to view camera video";
|
||||
"VoiceChat.TapToViewScreenVideo" = "Tap to view screen sharing";
|
||||
|
||||
"VoiceChat.ShareScreen" = "Share Screen";
|
||||
"VoiceChat.StopScreenSharing" = "Stop Screen Sharing";
|
||||
"VoiceChat.ParticipantIsSpeaking" = "%1$@ is speaking";
|
||||
|
||||
"WallpaperPreview.WallpaperColors" = "Colors";
|
||||
|
||||
"VoiceChat.UnmuteSuggestion" = "You are on mute. Tap here to speak.";
|
||||
|
||||
"VoiceChat.ContextAudio" = "Audio";
|
||||
|
||||
"VoiceChat.VideoPaused" = "Video is paused";
|
||||
"VoiceChat.YouAreSharingScreen" = "You are sharing your screen";
|
||||
"VoiceChat.StopScreenSharingShort" = "Stop Sharing";
|
||||
|
||||
"VoiceChat.OpenGroup" = "Open Group";
|
||||
|
||||
"VoiceChat.NoiseSuppression" = "Noise Suppression";
|
||||
"VoiceChat.NoiseSuppressionEnabled" = "Enabled";
|
||||
"VoiceChat.NoiseSuppressionDisabled" = "Disabled";
|
||||
|
||||
"VoiceChat.Unpin" = "Unpin";
|
||||
|
||||
"VoiceChat.VideoParticipantsLimitExceeded" = "Video is only available\nfor the first %@ members";
|
||||
|
||||
"ImportStickerPack.StickerCount_1" = "1 Sticker";
|
||||
"ImportStickerPack.StickerCount_2" = "2 Stickers";
|
||||
"ImportStickerPack.StickerCount_3_10" = "%@ Stickers";
|
||||
"ImportStickerPack.StickerCount_any" = "%@ Stickers";
|
||||
"ImportStickerPack.StickerCount_many" = "%@ Stickers";
|
||||
"ImportStickerPack.StickerCount_0" = "%@ Stickers";
|
||||
"ImportStickerPack.CreateStickerSet" = "Create Sticker Set";
|
||||
"ImportStickerPack.CreateNewStickerSet" = "Create a New Sticker Set";
|
||||
"ImportStickerPack.AddToExistingStickerSet" = "Add to an Existing Sticker Set";
|
||||
"ImportStickerPack.ChooseStickerSet" = "Choose Sticker Set";
|
||||
"ImportStickerPack.RemoveFromImport" = "Remove From Import";
|
||||
"ImportStickerPack.ChooseName" = "Choose Name";
|
||||
"ImportStickerPack.ChooseNameDescription" = "Please choose a name for your set.";
|
||||
"ImportStickerPack.NamePlaceholder" = "Name";
|
||||
"ImportStickerPack.GeneratingLink" = "generating link...";
|
||||
"ImportStickerPack.CheckingLink" = "checking availability...";
|
||||
"ImportStickerPack.ChooseLink" = "Choose Link";
|
||||
"ImportStickerPack.ChooseLinkDescription" = "You can use a-z, 0-9 and underscores.";
|
||||
"ImportStickerPack.LinkTaken" = "Sorry, this link is already taken.";
|
||||
"ImportStickerPack.LinkAvailable" = "Link is available.";
|
||||
"ImportStickerPack.ImportingStickers" = "Importing Stickers";
|
||||
"ImportStickerPack.Of" = "%1$@ of %2$@ Imported";
|
||||
"ImportStickerPack.InProgress" = "Please keep this window open\nuntil the import is completed.";
|
||||
"ImportStickerPack.Create" = "Create";
|
||||
|
||||
"WallpaperPreview.PreviewBottomTextAnimatable" = "Tap the play button to view the background animation.";
|
||||
|
||||
"Conversation.InputMenu" = "Menu";
|
||||
"Conversation.MessageDoesntExist" = "Message doesn't exist";
|
||||
|
||||
"Settings.CheckPasswordTitle" = "Your Password";
|
||||
"Settings.CheckPasswordText" = "Your account is protected by 2-Step Verification. Do you still remember your password?";
|
||||
"Settings.KeepPassword" = "Yes, definitely";
|
||||
"Settings.TryEnterPassword" = "Not sure, let me try";
|
||||
|
||||
"TwoFactorSetup.PasswordRecovery.Title" = "Create New Password";
|
||||
"TwoFactorSetup.PasswordRecovery.Text" = "You can now set a new password that will be used to log into your account.";
|
||||
"TwoFactorSetup.PasswordRecovery.PlaceholderPassword" = "New Password";
|
||||
"TwoFactorSetup.PasswordRecovery.PlaceholderConfirmPassword" = "Re-enter New Password";
|
||||
"TwoFactorSetup.PasswordRecovery.Action" = "Continue";
|
||||
"TwoFactorSetup.PasswordRecovery.Skip" = "Skip";
|
||||
"TwoFactorSetup.PasswordRecovery.SkipAlertTitle" = "Attention!";
|
||||
"TwoFactorSetup.PasswordRecovery.SkipAlertText" = "Skipping this step will disable 2-step verification for your account. Are you sure you want to skip?";
|
||||
"TwoFactorSetup.PasswordRecovery.SkipAlertAction" = "Skip";
|
||||
|
||||
"TwoStepAuth.RecoveryUnavailableResetTitle" = "Reset Password";
|
||||
"TwoStepAuth.RecoveryUnavailableResetText" = "Since you didn’t provide a recovery email when setting up your password, your remaining options are either to remember your password or wait 7 days until your password is reset.";
|
||||
"TwoStepAuth.RecoveryEmailResetText" = "If you don't have access to your recovery email, your remaining options are either to remember your password or wait 7 days until your password resets.";
|
||||
"TwoStepAuth.RecoveryUnavailableResetAction" = "Reset";
|
||||
"TwoStepAuth.ResetPendingText" = "You can reset your password in %@.";
|
||||
"TwoStepAuth.CancelResetTitle" = "Cancel Reset";
|
||||
"TwoStepAuth.ResetAction" = "Reset Password";
|
||||
"TwoStepAuth.CancelResetText" = "Cancel the password reset process? If you request a new reset later, it will take another 7 days.";
|
||||
"TwoStepAuth.RecoveryEmailResetNoAccess" = "Can’t access your email?";
|
||||
|
||||
"TwoFactorSetup.ResetDone.Title" = "New Password Set!";
|
||||
"TwoFactorSetup.ResetDone.Text" = "This password will be required when you log in on a new device in addition to the code you get via SMS.";
|
||||
"TwoFactorSetup.ResetDone.Action" = "Continue";
|
||||
|
||||
"TwoFactorSetup.ResetDone.TitleNoPassword" = "Password Removed";
|
||||
"TwoFactorSetup.ResetDone.TextNoPassword" = "You can always set a new password in\n\n\nSettings>Privacy & Security>Two-Step Verification";
|
||||
|
||||
"TwoFactorSetup.ResetFloodWait" = "You recently requested a password reset that was cancelled. Please wait %@ before making a new request.";
|
||||
"TwoFactorSetup.ResetFloodWait" = "You have recently requested a password reset that was canceled. Please wait for %@ before making a new request.";
|
||||
|
||||
"TwoFactorRemember.Title" = "Enter Your Password";
|
||||
"TwoFactorRemember.Text" = "Do you still remeber your password?";
|
||||
"TwoFactorRemember.Placeholder" = "Password";
|
||||
"TwoFactorRemember.Forgot" = "Forgot Password?";
|
||||
"TwoFactorRemember.CheckPassword" = "Check Password";
|
||||
"TwoFactorRemember.WrongPassword" = "This password is incorrect.";
|
||||
"TwoFactorRemember.Done.Title" = "Perfect!";
|
||||
"TwoFactorRemember.Done.Text" = "You still remember your password.";
|
||||
"TwoFactorRemember.Done.Action" = "Back to Settings";
|
||||
|
@ -1,31 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>${APP_NAME}</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>XPC!</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(PRODUCT_BUNDLE_SHORT_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${BUILD_NUMBER}</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
<string>com.apple.widget-extension</string>
|
||||
<key>NSExtensionPrincipalClass</key>
|
||||
<string>TodayViewController</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
@ -1,172 +0,0 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import WidgetItems
|
||||
|
||||
private extension UIColor {
|
||||
convenience init(rgb: UInt32) {
|
||||
self.init(red: CGFloat((rgb >> 16) & 0xff) / 255.0, green: CGFloat((rgb >> 8) & 0xff) / 255.0, blue: CGFloat(rgb & 0xff) / 255.0, alpha: 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
private let UIScreenScale = UIScreen.main.scale
|
||||
private func floorToScreenPixels(_ value: CGFloat) -> CGFloat {
|
||||
return floor(value * UIScreenScale) / UIScreenScale
|
||||
}
|
||||
|
||||
private let gradientColors: [NSArray] = [
|
||||
[UIColor(rgb: 0xff516a).cgColor, UIColor(rgb: 0xff885e).cgColor],
|
||||
[UIColor(rgb: 0xffa85c).cgColor, UIColor(rgb: 0xffcd6a).cgColor],
|
||||
[UIColor(rgb: 0x665fff).cgColor, UIColor(rgb: 0x82b1ff).cgColor],
|
||||
[UIColor(rgb: 0x54cb68).cgColor, UIColor(rgb: 0xa0de7e).cgColor],
|
||||
[UIColor(rgb: 0x4acccd).cgColor, UIColor(rgb: 0x00fcfd).cgColor],
|
||||
[UIColor(rgb: 0x2a9ef1).cgColor, UIColor(rgb: 0x72d5fd).cgColor],
|
||||
[UIColor(rgb: 0xd669ed).cgColor, UIColor(rgb: 0xe0a2f3).cgColor],
|
||||
]
|
||||
|
||||
private func avatarRoundImage(size: CGSize, source: UIImage) -> UIImage? {
|
||||
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
|
||||
let context = UIGraphicsGetCurrentContext()
|
||||
|
||||
context?.beginPath()
|
||||
context?.addEllipse(in: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height))
|
||||
context?.clip()
|
||||
|
||||
source.draw(in: CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
let image = UIGraphicsGetImageFromCurrentImageContext()
|
||||
UIGraphicsEndImageContext()
|
||||
return image
|
||||
}
|
||||
|
||||
private let deviceColorSpace: CGColorSpace = {
|
||||
if #available(iOSApplicationExtension 9.3, *) {
|
||||
if let colorSpace = CGColorSpace(name: CGColorSpace.displayP3) {
|
||||
return colorSpace
|
||||
} else {
|
||||
return CGColorSpaceCreateDeviceRGB()
|
||||
}
|
||||
} else {
|
||||
return CGColorSpaceCreateDeviceRGB()
|
||||
}
|
||||
}()
|
||||
|
||||
private func avatarViewLettersImage(size: CGSize, peerId: Int64, accountPeerId: Int64, letters: [String]) -> UIImage? {
|
||||
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
|
||||
let context = UIGraphicsGetCurrentContext()
|
||||
|
||||
context?.beginPath()
|
||||
context?.addEllipse(in: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height))
|
||||
context?.clip()
|
||||
|
||||
let colorIndex = abs(Int(accountPeerId + peerId))
|
||||
|
||||
let colorsArray = gradientColors[colorIndex % gradientColors.count]
|
||||
var locations: [CGFloat] = [1.0, 0.0]
|
||||
let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray, locations: &locations)!
|
||||
|
||||
context?.drawLinearGradient(gradient, start: CGPoint(), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
|
||||
|
||||
context?.setBlendMode(.normal)
|
||||
|
||||
let string = letters.count == 0 ? "" : (letters[0] + (letters.count == 1 ? "" : letters[1]))
|
||||
let attributedString = NSAttributedString(string: string, attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 20.0), NSAttributedString.Key.foregroundColor: UIColor.white])
|
||||
|
||||
let line = CTLineCreateWithAttributedString(attributedString)
|
||||
let lineBounds = CTLineGetBoundsWithOptions(line, .useGlyphPathBounds)
|
||||
|
||||
let lineOffset = CGPoint(x: string == "B" ? 1.0 : 0.0, y: 0.0)
|
||||
let lineOrigin = CGPoint(x: floorToScreenPixels(-lineBounds.origin.x + (size.width - lineBounds.size.width) / 2.0) + lineOffset.x, y: floorToScreenPixels(-lineBounds.origin.y + (size.height - lineBounds.size.height) / 2.0))
|
||||
|
||||
context?.translateBy(x: size.width / 2.0, y: size.height / 2.0)
|
||||
context?.scaleBy(x: 1.0, y: -1.0)
|
||||
context?.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
|
||||
|
||||
context?.translateBy(x: lineOrigin.x, y: lineOrigin.y)
|
||||
if let context = context {
|
||||
CTLineDraw(line, context)
|
||||
}
|
||||
context?.translateBy(x: -lineOrigin.x, y: -lineOrigin.y)
|
||||
|
||||
let image = UIGraphicsGetImageFromCurrentImageContext()
|
||||
UIGraphicsEndImageContext()
|
||||
return image
|
||||
}
|
||||
|
||||
private let avatarSize = CGSize(width: 50.0, height: 50.0)
|
||||
|
||||
private final class AvatarView: UIImageView {
|
||||
init(accountPeerId: Int64, peer: WidgetDataPeer, size: CGSize) {
|
||||
super.init(frame: CGRect())
|
||||
|
||||
if let path = peer.avatarPath, let image = UIImage(contentsOfFile: path), let roundImage = avatarRoundImage(size: size, source: image) {
|
||||
self.image = roundImage
|
||||
} else {
|
||||
self.image = avatarViewLettersImage(size: size, peerId: peer.id, accountPeerId: accountPeerId, letters: peer.letters)
|
||||
}
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
||||
final class PeerView: UIView {
|
||||
let peer: WidgetDataPeer
|
||||
private let avatarView: AvatarView
|
||||
private let titleLabel: UILabel
|
||||
|
||||
private let tapped: () -> Void
|
||||
|
||||
init(primaryColor: UIColor, accountPeerId: Int64, peer: WidgetDataPeer, tapped: @escaping () -> Void) {
|
||||
self.peer = peer
|
||||
self.tapped = tapped
|
||||
self.avatarView = AvatarView(accountPeerId: accountPeerId, peer: peer, size: avatarSize)
|
||||
|
||||
self.titleLabel = UILabel()
|
||||
var title = peer.name
|
||||
if let lastName = peer.lastName, !lastName.isEmpty {
|
||||
title.append("\n")
|
||||
title.append(lastName)
|
||||
}
|
||||
|
||||
let systemFontSize = UIFont.preferredFont(forTextStyle: .body).pointSize
|
||||
let fontSize = floor(systemFontSize * 11.0 / 17.0)
|
||||
|
||||
self.titleLabel.text = title
|
||||
if #available(iOSApplicationExtension 13.0, *) {
|
||||
self.titleLabel.textColor = UIColor.label
|
||||
} else {
|
||||
self.titleLabel.textColor = primaryColor
|
||||
}
|
||||
self.titleLabel.font = UIFont.systemFont(ofSize: fontSize)
|
||||
self.titleLabel.lineBreakMode = .byTruncatingTail
|
||||
self.titleLabel.numberOfLines = 2
|
||||
self.titleLabel.textAlignment = .center
|
||||
|
||||
super.init(frame: CGRect())
|
||||
|
||||
self.addSubview(self.avatarView)
|
||||
self.addSubview(self.titleLabel)
|
||||
|
||||
self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize) {
|
||||
self.avatarView.frame = CGRect(origin: CGPoint(x: floor((size.width - avatarSize.width) / 2.0), y: 0.0), size: avatarSize)
|
||||
|
||||
var titleSize = self.titleLabel.sizeThatFits(size)
|
||||
titleSize.width = min(size.width - 6.0, ceil(titleSize.width))
|
||||
titleSize.height = ceil(titleSize.height)
|
||||
self.titleLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: avatarSize.height + 5.0), size: titleSize)
|
||||
}
|
||||
|
||||
@objc func tapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
self.tapped()
|
||||
}
|
||||
}
|
||||
}
|
@ -1,171 +0,0 @@
|
||||
import UIKit
|
||||
import NotificationCenter
|
||||
import BuildConfig
|
||||
import WidgetItems
|
||||
import AppLockState
|
||||
|
||||
private func rootPathForBasePath(_ appGroupPath: String) -> String {
|
||||
return appGroupPath + "/telegram-data"
|
||||
}
|
||||
|
||||
@objc(TodayViewController)
|
||||
class TodayViewController: UIViewController, NCWidgetProviding {
|
||||
private var initializedInterface = false
|
||||
|
||||
private var buildConfig: BuildConfig?
|
||||
|
||||
private var primaryColor: UIColor = .black
|
||||
private var placeholderLabel: UILabel?
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
let appBundleIdentifier = Bundle.main.bundleIdentifier!
|
||||
guard let lastDotRange = appBundleIdentifier.range(of: ".", options: [.backwards]) else {
|
||||
return
|
||||
}
|
||||
let baseAppBundleId = String(appBundleIdentifier[..<lastDotRange.lowerBound])
|
||||
|
||||
let buildConfig = BuildConfig(baseAppBundleId: baseAppBundleId)
|
||||
self.buildConfig = buildConfig
|
||||
|
||||
let appGroupName = "group.\(baseAppBundleId)"
|
||||
let maybeAppGroupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupName)
|
||||
|
||||
guard let appGroupUrl = maybeAppGroupUrl else {
|
||||
return
|
||||
}
|
||||
|
||||
let rootPath = rootPathForBasePath(appGroupUrl.path)
|
||||
|
||||
let presentationData: WidgetPresentationData
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: widgetPresentationDataPath(rootPath: rootPath))), let value = try? JSONDecoder().decode(WidgetPresentationData.self, from: data) {
|
||||
presentationData = value
|
||||
} else {
|
||||
presentationData = WidgetPresentationData(applicationLockedString: "Unlock the app to use the widget", applicationStartRequiredString: "Open the app to use the widget", widgetGalleryTitle: "", widgetGalleryDescription: "")
|
||||
}
|
||||
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: appLockStatePath(rootPath: rootPath))), let state = try? JSONDecoder().decode(LockState.self, from: data), isAppLocked(state: state) {
|
||||
self.setPlaceholderText(presentationData.applicationLockedString)
|
||||
return
|
||||
}
|
||||
|
||||
if self.initializedInterface {
|
||||
return
|
||||
}
|
||||
self.initializedInterface = true
|
||||
|
||||
let dataPath = rootPath + "/widget-data"
|
||||
|
||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: dataPath)), let widgetData = try? JSONDecoder().decode(WidgetData.self, from: data) {
|
||||
self.setWidgetData(widgetData: widgetData, presentationData: presentationData)
|
||||
}
|
||||
}
|
||||
|
||||
private func setPlaceholderText(_ text: String) {
|
||||
let fontSize = UIFont.preferredFont(forTextStyle: .body).pointSize
|
||||
let placeholderLabel = UILabel()
|
||||
if #available(iOSApplicationExtension 13.0, *) {
|
||||
placeholderLabel.textColor = UIColor.label
|
||||
} else {
|
||||
placeholderLabel.textColor = self.primaryColor
|
||||
}
|
||||
placeholderLabel.font = UIFont.systemFont(ofSize: fontSize)
|
||||
placeholderLabel.text = text
|
||||
placeholderLabel.sizeToFit()
|
||||
self.placeholderLabel = placeholderLabel
|
||||
self.view.addSubview(placeholderLabel)
|
||||
}
|
||||
|
||||
func widgetPerformUpdate(completionHandler: (@escaping (NCUpdateResult) -> Void)) {
|
||||
completionHandler(.newData)
|
||||
}
|
||||
|
||||
@available(iOSApplicationExtension 10.0, *)
|
||||
func widgetActiveDisplayModeDidChange(_ activeDisplayMode: NCWidgetDisplayMode, withMaximumSize maxSize: CGSize) {
|
||||
|
||||
}
|
||||
|
||||
private var widgetData: WidgetData?
|
||||
|
||||
private func setWidgetData(widgetData: WidgetData, presentationData: WidgetPresentationData) {
|
||||
self.widgetData = widgetData
|
||||
self.peerViews.forEach {
|
||||
$0.removeFromSuperview()
|
||||
}
|
||||
self.peerViews = []
|
||||
switch widgetData {
|
||||
case .notAuthorized, .disabled:
|
||||
break
|
||||
case let .peers(peers):
|
||||
for peer in peers.peers {
|
||||
let peerView = PeerView(primaryColor: self.primaryColor, accountPeerId: peers.accountPeerId, peer: peer, tapped: { [weak self] in
|
||||
if let strongSelf = self, let buildConfig = strongSelf.buildConfig {
|
||||
if let url = URL(string: "\(buildConfig.appSpecificUrlScheme)://localpeer?id=\(peer.id)") {
|
||||
strongSelf.extensionContext?.open(url, completionHandler: nil)
|
||||
}
|
||||
}
|
||||
})
|
||||
self.view.addSubview(peerView)
|
||||
self.peerViews.append(peerView)
|
||||
}
|
||||
}
|
||||
|
||||
if self.peerViews.isEmpty {
|
||||
self.setPlaceholderText(presentationData.applicationStartRequiredString)
|
||||
} else {
|
||||
self.placeholderLabel?.removeFromSuperview()
|
||||
self.placeholderLabel = nil
|
||||
}
|
||||
|
||||
if let size = self.validLayout {
|
||||
self.updateLayout(size: size)
|
||||
}
|
||||
}
|
||||
|
||||
private var validLayout: CGSize?
|
||||
|
||||
private var peerViews: [PeerView] = []
|
||||
|
||||
override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
|
||||
self.updateLayout(size: self.view.bounds.size)
|
||||
}
|
||||
|
||||
private func updateLayout(size: CGSize) {
|
||||
self.validLayout = size
|
||||
|
||||
if let placeholderLabel = self.placeholderLabel {
|
||||
placeholderLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - placeholderLabel.bounds.width) / 2.0), y: floor((size.height - placeholderLabel.bounds.height) / 2.0)), size: placeholderLabel.bounds.size)
|
||||
}
|
||||
|
||||
let peerSize = CGSize(width: 70.0, height: 100.0)
|
||||
|
||||
var peerFrames: [CGRect] = []
|
||||
|
||||
var offset: CGFloat = 0.0
|
||||
for _ in self.peerViews {
|
||||
let peerFrame = CGRect(origin: CGPoint(x: offset, y: 10.0), size: peerSize)
|
||||
offset += peerFrame.size.width
|
||||
if peerFrame.maxX > size.width {
|
||||
break
|
||||
}
|
||||
peerFrames.append(peerFrame)
|
||||
}
|
||||
|
||||
var totalSize: CGFloat = 0.0
|
||||
for i in 0 ..< peerFrames.count {
|
||||
totalSize += peerFrames[i].width
|
||||
}
|
||||
|
||||
let spacing: CGFloat = floor((size.width - totalSize) / CGFloat(peerFrames.count))
|
||||
offset = floor(spacing / 2.0)
|
||||
for i in 0 ..< peerFrames.count {
|
||||
let peerView = self.peerViews[i]
|
||||
peerView.frame = CGRect(origin: CGPoint(x: offset, y: 16.0), size: peerFrames[i].size)
|
||||
peerView.updateLayout(size: peerFrames[i].size)
|
||||
offset += peerFrames[i].width + spacing
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
#ifndef Widget_Bridging_Header_h
|
||||
#define Widget_Bridging_Header_h
|
||||
|
||||
#endif
|
@ -1 +0,0 @@
|
||||
"CFBundleDisplayName" = "الأشخاص";
|
@ -1 +0,0 @@
|
||||
"CFBundleDisplayName" = "Leute";
|
@ -1 +0,0 @@
|
||||
"CFBundleDisplayName" = "People";
|
@ -1,2 +0,0 @@
|
||||
"Widget.NoUsers" = "No users here yet...";
|
||||
"Widget.AuthRequired" = "Open Telegram and log in.";
|
@ -1 +0,0 @@
|
||||
"CFBundleDisplayName" = "Personas";
|
@ -1 +0,0 @@
|
||||
"CFBundleDisplayName" = "Persone";
|
@ -1 +0,0 @@
|
||||
"CFBundleDisplayName" = "사람";
|
@ -1 +0,0 @@
|
||||
"CFBundleDisplayName" = "Mensen";
|
@ -1 +0,0 @@
|
||||
"CFBundleDisplayName" = "Pessoas";
|
@ -1 +0,0 @@
|
||||
"CFBundleDisplayName" = "Люди";
|
@ -92,7 +92,7 @@ private func getCommonTimeline(friends: [Friend]?, in context: TimelineProviderC
|
||||
|
||||
let rootPath = rootPathForBasePath(appGroupUrl.path)
|
||||
|
||||
TempBox.initializeShared(basePath: rootPath, processType: "widget", launchSpecificId: arc4random64())
|
||||
TempBox.initializeShared(basePath: rootPath, processType: "widget", launchSpecificId: Int64.random(in: Int64.min ... Int64.max))
|
||||
|
||||
let logsPath = rootPath + "/widget-logs"
|
||||
let _ = try? FileManager.default.createDirectory(atPath: logsPath, withIntermediateDirectories: true, attributes: nil)
|
||||
@ -130,7 +130,7 @@ private func getCommonTimeline(friends: [Friend]?, in context: TimelineProviderC
|
||||
|
||||
var friendsByAccount: [Signal<[ParsedPeer], NoError>] = []
|
||||
for (accountId, items) in itemsByAccount {
|
||||
friendsByAccount.append(accountTransaction(rootPath: rootPath, id: AccountRecordId(rawValue: accountId), encryptionParameters: encryptionParameters, isReadOnly: true, useCopy: true, transaction: { postbox, transaction -> [ParsedPeer] in
|
||||
friendsByAccount.append(accountTransaction(rootPath: rootPath, id: AccountRecordId(rawValue: accountId), encryptionParameters: encryptionParameters, isReadOnly: true, useCopy: false, transaction: { postbox, transaction -> [ParsedPeer] in
|
||||
guard let state = transaction.getState() as? AuthorizedAccountState else {
|
||||
return []
|
||||
}
|
||||
|
@ -56,3 +56,10 @@ http_file(
|
||||
urls = ["https://github.com/Kitware/CMake/releases/download/v3.19.2/cmake-3.19.2-macos-universal.tar.gz"],
|
||||
sha256 = "50afa2cb66bea6a0314ef28034f3ff1647325e30cf5940f97906a56fd9640bd8",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "appcenter_sdk",
|
||||
urls = ["https://github.com/microsoft/appcenter-sdk-apple/releases/download/4.1.1/AppCenter-SDK-Apple-4.1.1.zip"],
|
||||
sha256 = "032907801dc7784744a1ca8fd40d3eecc34a2e27a93a4b3993f617cca204a9f3",
|
||||
build_file = "@//third-party/AppCenter:AppCenter.BUILD",
|
||||
)
|
||||
|
@ -106,6 +106,15 @@ class BazelCommandLine:
|
||||
def set_build_number(self, build_number):
|
||||
self.build_number = build_number
|
||||
|
||||
def set_custom_target(self, target_name):
|
||||
self.custom_target = target_name
|
||||
|
||||
def set_continue_on_error(self, continue_on_error):
|
||||
self.continue_on_error = continue_on_error
|
||||
|
||||
def set_enable_sandbox(self, enable_sandbox):
|
||||
self.enable_sandbox = enable_sandbox
|
||||
|
||||
def set_split_swiftmodules(self, value):
|
||||
self.split_submodules = value
|
||||
|
||||
@ -260,10 +269,18 @@ class BazelCommandLine:
|
||||
self.build_environment.bazel_path
|
||||
]
|
||||
combined_arguments += self.get_startup_bazel_arguments()
|
||||
combined_arguments += [
|
||||
'build',
|
||||
'Telegram/Telegram'
|
||||
]
|
||||
combined_arguments += ['build']
|
||||
|
||||
if self.custom_target is not None:
|
||||
combined_arguments += [self.custom_target]
|
||||
else:
|
||||
combined_arguments += ['Telegram/Telegram']
|
||||
|
||||
if self.continue_on_error:
|
||||
combined_arguments += ['--keep_going']
|
||||
|
||||
if self.enable_sandbox:
|
||||
combined_arguments += ['--spawn_strategy=sandboxed']
|
||||
|
||||
if self.configuration_path is None:
|
||||
raise Exception('configuration_path is not defined')
|
||||
@ -353,10 +370,15 @@ def generate_project(arguments):
|
||||
bazel_command_line.set_build_number(arguments.buildNumber)
|
||||
|
||||
disable_extensions = False
|
||||
disable_provisioning_profiles = False
|
||||
generate_dsym = False
|
||||
|
||||
if arguments.disableExtensions is not None:
|
||||
disable_extensions = arguments.disableExtensions
|
||||
if arguments.disableProvisioningProfiles is not None:
|
||||
disable_provisioning_profiles = arguments.disableProvisioningProfiles
|
||||
if arguments.generateDsym is not None:
|
||||
generate_dsym = arguments.generateDsym
|
||||
|
||||
call_executable(['killall', 'Xcode'], check_result=False)
|
||||
|
||||
@ -364,6 +386,7 @@ def generate_project(arguments):
|
||||
build_environment=bazel_command_line.build_environment,
|
||||
disable_extensions=disable_extensions,
|
||||
disable_provisioning_profiles=disable_provisioning_profiles,
|
||||
generate_dsym=generate_dsym,
|
||||
configuration_path=bazel_command_line.configuration_path,
|
||||
bazel_app_arguments=bazel_command_line.get_project_generation_arguments()
|
||||
)
|
||||
@ -386,6 +409,9 @@ def build(arguments):
|
||||
|
||||
bazel_command_line.set_configuration(arguments.configuration)
|
||||
bazel_command_line.set_build_number(arguments.buildNumber)
|
||||
bazel_command_line.set_custom_target(arguments.target)
|
||||
bazel_command_line.set_continue_on_error(arguments.continueOnError)
|
||||
bazel_command_line.set_enable_sandbox(arguments.sandbox)
|
||||
|
||||
bazel_command_line.set_split_swiftmodules(not arguments.disableParallelSwiftmoduleGeneration)
|
||||
|
||||
@ -512,6 +538,15 @@ if __name__ == '__main__':
|
||||
'''
|
||||
)
|
||||
|
||||
generateProjectParser.add_argument(
|
||||
'--generateDsym',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='''
|
||||
This improves profiling experinence by generating DSYM files. Keep disabled for better build performance.
|
||||
'''
|
||||
)
|
||||
|
||||
buildParser = subparsers.add_parser('build', help='Build the app')
|
||||
buildParser.add_argument(
|
||||
'--buildNumber',
|
||||
@ -540,6 +575,24 @@ if __name__ == '__main__':
|
||||
default=False,
|
||||
help='Generate .swiftmodule files in parallel to building modules, can speed up compilation on multi-core systems.'
|
||||
)
|
||||
buildParser.add_argument(
|
||||
'--target',
|
||||
type=str,
|
||||
help='A custom bazel target name to build.',
|
||||
metavar='target_name'
|
||||
)
|
||||
buildParser.add_argument(
|
||||
'--continueOnError',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Continue build process after an error.',
|
||||
)
|
||||
buildParser.add_argument(
|
||||
'--sandbox',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Enable sandbox.',
|
||||
)
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
parser.print_help()
|
||||
|
@ -10,7 +10,7 @@ def remove_directory(path):
|
||||
shutil.rmtree(path)
|
||||
|
||||
|
||||
def generate(build_environment: BuildEnvironment, disable_extensions, disable_provisioning_profiles, configuration_path, bazel_app_arguments):
|
||||
def generate(build_environment: BuildEnvironment, disable_extensions, disable_provisioning_profiles, generate_dsym, configuration_path, bazel_app_arguments):
|
||||
project_path = os.path.join(build_environment.base_path, 'build-input/gen/project')
|
||||
app_target = 'Telegram'
|
||||
|
||||
@ -81,6 +81,8 @@ def generate(build_environment: BuildEnvironment, disable_extensions, disable_pr
|
||||
bazel_build_arguments += ['--//Telegram:disableExtensions']
|
||||
if disable_provisioning_profiles:
|
||||
bazel_build_arguments += ['--//Telegram:disableProvisioningProfiles']
|
||||
if generate_dsym:
|
||||
bazel_build_arguments += ['--apple_generate_dsym']
|
||||
|
||||
call_executable([
|
||||
tulsi_path,
|
||||
|
@ -8,4 +8,5 @@ exports_files([
|
||||
"WatchApp.mobileprovision",
|
||||
"WatchExtension.mobileprovision",
|
||||
"Widget.mobileprovision",
|
||||
"BroadcastUpload.mobileprovision",
|
||||
])
|
||||
|
1
build_number_offset
Normal file
@ -0,0 +1 @@
|
||||
2300
|
@ -79,7 +79,8 @@ COMMIT_ID="$(git rev-parse HEAD)"
|
||||
COMMIT_AUTHOR=$(git log -1 --pretty=format:'%an')
|
||||
if [ -z "$2" ]; then
|
||||
COMMIT_COUNT=$(git rev-list --count HEAD)
|
||||
COMMIT_COUNT="$(($COMMIT_COUNT+2000))"
|
||||
BUILD_NUMBER_OFFSET="$(cat build_number_offset)"
|
||||
COMMIT_COUNT="$(($COMMIT_COUNT+$BUILD_NUMBER_OFFSET))"
|
||||
BUILD_NUMBER="$COMMIT_COUNT"
|
||||
else
|
||||
BUILD_NUMBER="$2"
|
||||
|
@ -3,84 +3,11 @@
|
||||
set -e
|
||||
set -x
|
||||
|
||||
API_HOST="https://api.appcenter.ms"
|
||||
IPA_PATH="build/artifacts/Telegram.ipa"
|
||||
DSYM_PATH="build/artifacts/Telegram.DSYMs.zip"
|
||||
|
||||
upload_ipa() {
|
||||
GROUP_DATA=$(curl \
|
||||
-X GET \
|
||||
--header "X-API-Token: $API_TOKEN" \
|
||||
"$API_HOST/v0.1/apps/$API_USER_NAME/$API_APP_NAME/distribution_groups/Internal" \
|
||||
)
|
||||
|
||||
GROUP_ID=$(echo "$GROUP_DATA" | python -c 'import json,sys; obj=json.load(sys.stdin); print obj["id"];')
|
||||
|
||||
UPLOAD_TOKEN=$(curl \
|
||||
-X POST \
|
||||
--header "Content-Type: application/json" \
|
||||
--header "Accept: application/json" \
|
||||
--header "X-API-Token: $API_TOKEN" \
|
||||
"$API_HOST/v0.1/apps/$API_USER_NAME/$API_APP_NAME/release_uploads" \
|
||||
)
|
||||
|
||||
|
||||
UPLOAD_URL=$(echo "$UPLOAD_TOKEN" | python -c 'import json,sys; obj=json.load(sys.stdin); print obj["upload_url"];')
|
||||
UPLOAD_ID=$(echo "$UPLOAD_TOKEN" | python -c 'import json,sys; obj=json.load(sys.stdin); print obj["upload_id"];')
|
||||
|
||||
curl --progress-bar -F "ipa=@${IPA_PATH}" "$UPLOAD_URL"
|
||||
|
||||
RELEASE_TOKEN=$(curl \
|
||||
-X PATCH \
|
||||
--header "Content-Type: application/json" \
|
||||
--header "Accept: application/json" \
|
||||
--header "X-API-Token: $API_TOKEN" \
|
||||
-d '{ "status": "committed" }' \
|
||||
"$API_HOST/v0.1/apps/$API_USER_NAME/$API_APP_NAME/release_uploads/$UPLOAD_ID" \
|
||||
)
|
||||
|
||||
|
||||
RELEASE_URL=$(echo "$RELEASE_TOKEN" | python -c 'import json,sys; obj=json.load(sys.stdin); print obj["release_url"];')
|
||||
RELEASE_ID=$(echo "$RELEASE_TOKEN" | python -c 'import json,sys; obj=json.load(sys.stdin); print obj["release_id"];')
|
||||
|
||||
curl \
|
||||
-X POST \
|
||||
--header "Content-Type: application/json" \
|
||||
--header "Accept: application/json" \
|
||||
--header "X-API-Token: $API_TOKEN" \
|
||||
-d "{ \"id\": \"$GROUP_ID\", \"mandatory_update\": false, \"notify_testers\": false }" \
|
||||
"$API_HOST/$RELEASE_URL/groups"
|
||||
}
|
||||
|
||||
upload_dsym() {
|
||||
UPLOAD_DSYM_DATA=$(curl \
|
||||
-X POST \
|
||||
--header "Content-Type: application/json" \
|
||||
--header "Accept: application/json" \
|
||||
--header "X-API-Token: $API_TOKEN" \
|
||||
-d "{ \"symbol_type\": \"Apple\"}" \
|
||||
"$API_HOST/v0.1/apps/$API_USER_NAME/$API_APP_NAME/symbol_uploads" \
|
||||
)
|
||||
|
||||
DSYM_UPLOAD_URL=$(echo "$UPLOAD_DSYM_DATA" | python -c 'import json,sys; obj=json.load(sys.stdin); print obj["upload_url"];')
|
||||
DSYM_UPLOAD_ID=$(echo "$UPLOAD_DSYM_DATA" | python -c 'import json,sys; obj=json.load(sys.stdin); print obj["symbol_upload_id"];')
|
||||
|
||||
curl \
|
||||
--progress-bar \
|
||||
--header "x-ms-blob-type: BlockBlob" \
|
||||
--upload-file "${DSYM_PATH}" \
|
||||
"$DSYM_UPLOAD_URL"
|
||||
|
||||
curl \
|
||||
-X PATCH \
|
||||
--header "Content-Type: application/json" \
|
||||
--header "Accept: application/json" \
|
||||
--header "X-API-Token: $API_TOKEN" \
|
||||
-d '{ "status": "committed" }' \
|
||||
"$API_HOST/v0.1/apps/$API_USER_NAME/$API_APP_NAME/symbol_uploads/$DSYM_UPLOAD_ID"
|
||||
}
|
||||
|
||||
APPCENTER="/usr/local/bin/appcenter"
|
||||
|
||||
$APPCENTER login --token "$API_TOKEN"
|
||||
$APPCENTER distribute release --app "$API_USER_NAME/$API_APP_NAME" -f "$IPA_PATH" -g Internal
|
||||
$APPCENTER crashes upload-symbols --app "$API_USER_NAME/$API_APP_NAME" --symbol "$DSYM_PATH"
|
||||
|
@ -30,6 +30,7 @@ public enum AccessType {
|
||||
|
||||
public final class TelegramApplicationBindings {
|
||||
public let isMainApp: Bool
|
||||
public let appBundleId: String
|
||||
public let containerPath: String
|
||||
public let appSpecificScheme: String
|
||||
public let openUrl: (String) -> Void
|
||||
@ -52,9 +53,11 @@ public final class TelegramApplicationBindings {
|
||||
public let getAvailableAlternateIcons: () -> [PresentationAppIcon]
|
||||
public let getAlternateIconName: () -> String?
|
||||
public let requestSetAlternateIconName: (String?, @escaping (Bool) -> Void) -> Void
|
||||
public let forceOrientation: (UIInterfaceOrientation) -> Void
|
||||
|
||||
public init(isMainApp: Bool, containerPath: String, appSpecificScheme: String, openUrl: @escaping (String) -> Void, openUniversalUrl: @escaping (String, TelegramApplicationOpenUrlCompletion) -> Void, canOpenUrl: @escaping (String) -> Bool, getTopWindow: @escaping () -> UIWindow?, displayNotification: @escaping (String) -> Void, applicationInForeground: Signal<Bool, NoError>, applicationIsActive: Signal<Bool, NoError>, clearMessageNotifications: @escaping ([MessageId]) -> Void, pushIdleTimerExtension: @escaping () -> Disposable, openSettings: @escaping () -> Void, openAppStorePage: @escaping () -> Void, registerForNotifications: @escaping (@escaping (Bool) -> Void) -> Void, requestSiriAuthorization: @escaping (@escaping (Bool) -> Void) -> Void, siriAuthorization: @escaping () -> AccessType, getWindowHost: @escaping () -> WindowHost?, presentNativeController: @escaping (UIViewController) -> Void, dismissNativeController: @escaping () -> Void, getAvailableAlternateIcons: @escaping () -> [PresentationAppIcon], getAlternateIconName: @escaping () -> String?, requestSetAlternateIconName: @escaping (String?, @escaping (Bool) -> Void) -> Void) {
|
||||
public init(isMainApp: Bool, appBundleId: String, containerPath: String, appSpecificScheme: String, openUrl: @escaping (String) -> Void, openUniversalUrl: @escaping (String, TelegramApplicationOpenUrlCompletion) -> Void, canOpenUrl: @escaping (String) -> Bool, getTopWindow: @escaping () -> UIWindow?, displayNotification: @escaping (String) -> Void, applicationInForeground: Signal<Bool, NoError>, applicationIsActive: Signal<Bool, NoError>, clearMessageNotifications: @escaping ([MessageId]) -> Void, pushIdleTimerExtension: @escaping () -> Disposable, openSettings: @escaping () -> Void, openAppStorePage: @escaping () -> Void, registerForNotifications: @escaping (@escaping (Bool) -> Void) -> Void, requestSiriAuthorization: @escaping (@escaping (Bool) -> Void) -> Void, siriAuthorization: @escaping () -> AccessType, getWindowHost: @escaping () -> WindowHost?, presentNativeController: @escaping (UIViewController) -> Void, dismissNativeController: @escaping () -> Void, getAvailableAlternateIcons: @escaping () -> [PresentationAppIcon], getAlternateIconName: @escaping () -> String?, requestSetAlternateIconName: @escaping (String?, @escaping (Bool) -> Void) -> Void, forceOrientation: @escaping (UIInterfaceOrientation) -> Void) {
|
||||
self.isMainApp = isMainApp
|
||||
self.appBundleId = appBundleId
|
||||
self.containerPath = containerPath
|
||||
self.appSpecificScheme = appSpecificScheme
|
||||
self.openUrl = openUrl
|
||||
@ -77,6 +80,7 @@ public final class TelegramApplicationBindings {
|
||||
self.getAvailableAlternateIcons = getAvailableAlternateIcons
|
||||
self.getAlternateIconName = getAlternateIconName
|
||||
self.requestSetAlternateIconName = requestSetAlternateIconName
|
||||
self.forceOrientation = forceOrientation
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,9 +154,9 @@ public struct ChatAvailableMessageActions {
|
||||
}
|
||||
|
||||
public enum WallpaperUrlParameter {
|
||||
case slug(String, WallpaperPresentationOptions, UIColor?, UIColor?, Int32?, Int32?)
|
||||
case slug(String, WallpaperPresentationOptions, [UInt32], Int32?, Int32?)
|
||||
case color(UIColor)
|
||||
case gradient(UIColor, UIColor, Int32?)
|
||||
case gradient([UInt32], Int32?)
|
||||
}
|
||||
|
||||
public enum ResolvedUrlSettingsSection {
|
||||
@ -167,7 +171,7 @@ public enum ResolvedUrl {
|
||||
case inaccessiblePeer
|
||||
case botStart(peerId: PeerId, payload: String)
|
||||
case groupBotStart(peerId: PeerId, payload: String)
|
||||
case channelMessage(peerId: PeerId, messageId: MessageId)
|
||||
case channelMessage(peerId: PeerId, messageId: MessageId, timecode: Double?)
|
||||
case replyThreadMessage(replyThreadMessage: ChatReplyThreadMessage, messageId: MessageId)
|
||||
case stickerPack(name: String)
|
||||
case instantView(TelegramMediaWebpage, String?)
|
||||
@ -184,6 +188,7 @@ public enum ResolvedUrl {
|
||||
#endif
|
||||
case settings(ResolvedUrlSettingsSection)
|
||||
case joinVoiceChat(PeerId, String?)
|
||||
case importStickers
|
||||
}
|
||||
|
||||
public enum NavigateToChatKeepStack {
|
||||
@ -216,17 +221,14 @@ public final class ChatPeerNearbyData: Equatable {
|
||||
|
||||
public final class ChatGreetingData: Equatable {
|
||||
public static func == (lhs: ChatGreetingData, rhs: ChatGreetingData) -> Bool {
|
||||
if let lhsSticker = lhs.sticker, let rhsSticker = rhs.sticker, !lhsSticker.isEqual(to: rhsSticker) {
|
||||
return false
|
||||
} else if (lhs.sticker == nil) != (rhs.sticker == nil) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
return lhs.uuid == rhs.uuid
|
||||
}
|
||||
|
||||
public let sticker: TelegramMediaFile?
|
||||
public let uuid: UUID
|
||||
public let sticker: Signal<TelegramMediaFile?, NoError>
|
||||
|
||||
public init(sticker: TelegramMediaFile?) {
|
||||
public init(uuid: UUID, sticker: Signal<TelegramMediaFile?, NoError>) {
|
||||
self.uuid = uuid
|
||||
self.sticker = sticker
|
||||
}
|
||||
}
|
||||
@ -282,14 +284,13 @@ public final class NavigateToChatControllerParams {
|
||||
public let activateMessageSearch: (ChatSearchDomain, String)?
|
||||
public let peekData: ChatPeekTimeout?
|
||||
public let peerNearbyData: ChatPeerNearbyData?
|
||||
public let greetingData: ChatGreetingData?
|
||||
public let reportReason: ReportReason?
|
||||
public let animated: Bool
|
||||
public let options: NavigationAnimationOptions
|
||||
public let parentGroupId: PeerGroupId?
|
||||
public let completion: (ChatController) -> Void
|
||||
|
||||
public init(navigationController: NavigationController, chatController: ChatController? = nil, context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?> = Atomic<ChatLocationContextHolder?>(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, updateTextInputState: ChatTextInputState? = nil, activateInput: Bool = false, keepStack: NavigateToChatKeepStack = .default, useExisting: Bool = true, purposefulAction: (() -> Void)? = nil, scrollToEndIfExists: Bool = false, activateMessageSearch: (ChatSearchDomain, String)? = nil, peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil, greetingData: ChatGreetingData? = nil, reportReason: ReportReason? = nil, animated: Bool = true, options: NavigationAnimationOptions = [], parentGroupId: PeerGroupId? = nil, completion: @escaping (ChatController) -> Void = { _ in }) {
|
||||
public init(navigationController: NavigationController, chatController: ChatController? = nil, context: AccountContext, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?> = Atomic<ChatLocationContextHolder?>(value: nil), subject: ChatControllerSubject? = nil, botStart: ChatControllerInitialBotStart? = nil, updateTextInputState: ChatTextInputState? = nil, activateInput: Bool = false, keepStack: NavigateToChatKeepStack = .default, useExisting: Bool = true, purposefulAction: (() -> Void)? = nil, scrollToEndIfExists: Bool = false, activateMessageSearch: (ChatSearchDomain, String)? = nil, peekData: ChatPeekTimeout? = nil, peerNearbyData: ChatPeerNearbyData? = nil, reportReason: ReportReason? = nil, animated: Bool = true, options: NavigationAnimationOptions = [], parentGroupId: PeerGroupId? = nil, completion: @escaping (ChatController) -> Void = { _ in }) {
|
||||
self.navigationController = navigationController
|
||||
self.chatController = chatController
|
||||
self.chatLocationContextHolder = chatLocationContextHolder
|
||||
@ -306,7 +307,6 @@ public final class NavigateToChatControllerParams {
|
||||
self.activateMessageSearch = activateMessageSearch
|
||||
self.peekData = peekData
|
||||
self.peerNearbyData = peerNearbyData
|
||||
self.greetingData = greetingData
|
||||
self.reportReason = reportReason
|
||||
self.animated = animated
|
||||
self.options = options
|
||||
@ -471,15 +471,17 @@ public final class ContactSelectionControllerParams {
|
||||
public let options: [ContactListAdditionalOption]
|
||||
public let displayDeviceContacts: Bool
|
||||
public let displayCallIcons: Bool
|
||||
public let multipleSelection: Bool
|
||||
public let confirmation: (ContactListPeer) -> Signal<Bool, NoError>
|
||||
|
||||
public init(context: AccountContext, autoDismiss: Bool = true, title: @escaping (PresentationStrings) -> String, options: [ContactListAdditionalOption] = [], displayDeviceContacts: Bool = false, displayCallIcons: Bool = false, confirmation: @escaping (ContactListPeer) -> Signal<Bool, NoError> = { _ in .single(true) }) {
|
||||
public init(context: AccountContext, autoDismiss: Bool = true, title: @escaping (PresentationStrings) -> String, options: [ContactListAdditionalOption] = [], displayDeviceContacts: Bool = false, displayCallIcons: Bool = false, multipleSelection: Bool = false, confirmation: @escaping (ContactListPeer) -> Signal<Bool, NoError> = { _ in .single(true) }) {
|
||||
self.context = context
|
||||
self.autoDismiss = autoDismiss
|
||||
self.title = title
|
||||
self.options = options
|
||||
self.displayDeviceContacts = displayDeviceContacts
|
||||
self.displayCallIcons = displayCallIcons
|
||||
self.multipleSelection = multipleSelection
|
||||
self.confirmation = confirmation
|
||||
}
|
||||
}
|
||||
@ -509,7 +511,7 @@ public enum ChatListSearchFilter: Equatable {
|
||||
case .voice:
|
||||
return 5
|
||||
case let .peer(peerId, _, _, _):
|
||||
return peerId.id
|
||||
return peerId.id._internalGetInt32Value()
|
||||
case let .date(_, date, _):
|
||||
return date
|
||||
}
|
||||
@ -544,6 +546,7 @@ public protocol RecentSessionsController: class {
|
||||
}
|
||||
|
||||
public protocol SharedAccountContext: class {
|
||||
var sharedContainerPath: String { get }
|
||||
var basePath: String { get }
|
||||
var mainWindow: Window1? { get }
|
||||
var accountManager: AccountManager { get }
|
||||
@ -588,7 +591,7 @@ public protocol SharedAccountContext: class {
|
||||
func makeComposeController(context: AccountContext) -> ViewController
|
||||
func makeChatListController(context: AccountContext, groupId: PeerGroupId, controlsHistoryPreload: Bool, hideNetworkActivityStatus: Bool, previewing: Bool, enableDebugActions: Bool) -> ChatListController
|
||||
func makeChatController(context: AccountContext, chatLocation: ChatLocation, subject: ChatControllerSubject?, botStart: ChatControllerInitialBotStart?, mode: ChatControllerPresentationMode) -> ChatController
|
||||
func makeChatMessagePreviewItem(context: AccountContext, messages: [Message], theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder, forcedResourceStatus: FileMediaResourceStatus?, tapMessage: ((Message) -> Void)?, clickThroughMessage: (() -> Void)?) -> ListViewItem
|
||||
func makeChatMessagePreviewItem(context: AccountContext, messages: [Message], theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder, forcedResourceStatus: FileMediaResourceStatus?, tapMessage: ((Message) -> Void)?, clickThroughMessage: (() -> Void)?, backgroundNode: ASDisplayNode?) -> ListViewItem
|
||||
func makeChatMessageDateHeaderItem(context: AccountContext, timestamp: Int32, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder) -> ListViewItemHeader
|
||||
func makePeerSharedMediaController(context: AccountContext, peerId: PeerId) -> ViewController?
|
||||
func makeContactSelectionController(_ params: ContactSelectionControllerParams) -> ContactSelectionController
|
||||
@ -604,7 +607,7 @@ public protocol SharedAccountContext: class {
|
||||
func openExternalUrl(context: AccountContext, urlContext: OpenURLContext, url: String, forceExternal: Bool, presentationData: PresentationData, navigationController: NavigationController?, dismissInput: @escaping () -> Void)
|
||||
func chatAvailableMessageActions(postbox: Postbox, accountPeerId: PeerId, messageIds: Set<MessageId>) -> Signal<ChatAvailableMessageActions, NoError>
|
||||
func chatAvailableMessageActions(postbox: Postbox, accountPeerId: PeerId, messageIds: Set<MessageId>, messages: [MessageId: Message], peers: [PeerId: Peer]) -> Signal<ChatAvailableMessageActions, NoError>
|
||||
func resolveUrl(account: Account, url: String, skipUrlAuth: Bool) -> Signal<ResolvedUrl, NoError>
|
||||
func resolveUrl(context: AccountContext, peerId: PeerId?, url: String, skipUrlAuth: Bool) -> Signal<ResolvedUrl, NoError>
|
||||
func openResolvedUrl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, openPeer: @escaping (PeerId, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?, requestMessageActionUrlAuth: ((MessageActionUrlSubject) -> Void)?, joinVoiceChat: ((PeerId, String?, CachedChannelData.ActiveCall) -> Void)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, contentContext: Any?)
|
||||
func openAddContact(context: AccountContext, firstName: String, lastName: String, phoneNumber: String, label: String, present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, completed: @escaping () -> Void)
|
||||
func openAddPersonContact(context: AccountContext, peerId: PeerId, pushController: @escaping (ViewController) -> Void, present: @escaping (ViewController, Any?) -> Void)
|
||||
@ -709,10 +712,12 @@ public protocol AccountGroupCallContextCache: class {
|
||||
public protocol AccountContext: class {
|
||||
var sharedContext: SharedAccountContext { get }
|
||||
var account: Account { get }
|
||||
var engine: TelegramEngine { get }
|
||||
|
||||
var liveLocationManager: LiveLocationManager? { get }
|
||||
var peersNearbyManager: PeersNearbyManager? { get }
|
||||
var fetchManager: FetchManager { get }
|
||||
var prefetchManager: PrefetchManager? { get }
|
||||
var downloadedMediaStoreManager: DownloadedMediaStoreManager { get }
|
||||
var peerChannelMemberCategoriesContextsManager: PeerChannelMemberCategoriesContextsManager { get }
|
||||
var wallpaperUploadManager: WallpaperUploadManager? { get }
|
||||
@ -731,6 +736,7 @@ public protocol AccountContext: class {
|
||||
func chatLocationOutgoingReadState(for location: ChatLocation, contextHolder: Atomic<ChatLocationContextHolder?>) -> Signal<MessageId?, NoError>
|
||||
func applyMaxReadIndex(for location: ChatLocation, contextHolder: Atomic<ChatLocationContextHolder?>, messageIndex: MessageIndex)
|
||||
|
||||
func scheduleGroupCall(peerId: PeerId)
|
||||
func joinGroupCall(peerId: PeerId, invite: String?, requestJoinAsPeerId: ((@escaping (PeerId?) -> Void) -> Void)?, activeCall: CachedChannelData.ActiveCall)
|
||||
func requestCall(peerId: PeerId, isVideo: Bool, completion: @escaping () -> Void)
|
||||
}
|
||||
|