Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2019-11-22 20:17:53 +04:00
commit 91fb33f71e
73 changed files with 5089 additions and 3454 deletions

View file

@ -1,6 +1,8 @@
stages:
- build
- deploy
- verifysanity
- verify
variables:
LANG: "en_US.UTF-8"
@ -50,3 +52,39 @@ deploy_beta_testflight:
- bash buildbox/deploy-telegram.sh appstore
environment:
name: testflight_llc
verifysanity_beta_testflight:
tags:
- ios_beta
stage: verifysanity
only:
- beta
except:
- tags
script:
- bash buildbox/verify-telegram.sh appstore cached
environment:
name: testflight_llc
artifacts:
when: on_failure
paths:
- build/verifysanity_artifacts
expire_in: 1 week
verify_beta_testflight:
tags:
- ios_beta
stage: verify
only:
- beta
except:
- tags
script:
- bash buildbox/verify-telegram.sh appstore full
environment:
name: testflight_llc
artifacts:
when: on_failure
paths:
- build/verify_artifacts
expire_in: 1 week

View file

@ -3,7 +3,7 @@
include Utils.makefile
BUCK_OPTIONS=\
--config custom.appVersion="5.12.2" \
--config custom.appVersion="5.13" \
--config custom.developmentCodeSignIdentity="${DEVELOPMENT_CODE_SIGN_IDENTITY}" \
--config custom.distributionCodeSignIdentity="${DISTRIBUTION_CODE_SIGN_IDENTITY}" \
--config custom.developmentTeam="${DEVELOPMENT_TEAM}" \
@ -356,7 +356,7 @@ build_verbose: check_env
//:NotificationContentExtension#dwarf-and-dsym,iphoneos-arm64 \
//:NotificationServiceExtension#dwarf-and-dsym,iphoneos-arm64 \
//:IntentsExtension#dwarf-and-dsym,iphoneos-arm64 \
--verbose 8 ${BUCK_OPTIONS} ${BUCK_THREADS_OPTIONS} ${BUCK_DEBUG_OPTIONS}
--verbose 8 ${BUCK_OPTIONS} ${BUCK_THREADS_OPTIONS} ${BUCK_DEBUG_OPTIONS} ${BUCK_CACHE_OPTIONS}
deps: check_env
$(BUCK) query "deps(//:AppPackage)" --dot \

View file

@ -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, languagesCategory: languagesCategory, encryptionParameters: encryptionParameters, appVersion: appVersion, bundleData: buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), setPreferredContentSize: { [weak self] size in
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?.preferredContentSize = size
})
}

View file

@ -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, languagesCategory: languagesCategory, encryptionParameters: encryptionParameters, appVersion: appVersion, bundleData: buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), getExtensionContext: { [weak self] in
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
return self?.extensionContext
})
}

View file

@ -69,6 +69,7 @@ public class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo
let buildConfig = BuildConfig(baseAppBundleId: baseAppBundleId)
let apiId: Int32 = buildConfig.apiId
let apiHash: String = buildConfig.apiHash
let languagesCategory = "ios"
let appGroupName = "group.\(baseAppBundleId)"
@ -100,7 +101,7 @@ public class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchFo
let deviceSpecificEncryptionParameters = BuildConfig.deviceSpecificEncryptionParameters(rootPath, baseAppBundleId: baseAppBundleId)
let encryptionParameters = ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: deviceSpecificEncryptionParameters.key)!, salt: ValueBoxEncryptionParameters.Salt(data: deviceSpecificEncryptionParameters.salt)!)
account = currentAccount(allocateIfNotExists: false, networkArguments: NetworkInitializationArguments(apiId: apiId, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: 0, appData: .single(buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider()), supplementary: true, manager: accountManager, rootPath: rootPath, auxiliaryMethods: accountAuxiliaryMethods, encryptionParameters: encryptionParameters)
account = currentAccount(allocateIfNotExists: false, networkArguments: NetworkInitializationArguments(apiId: apiId, apiHash: apiHash, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: 0, appData: .single(buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider()), supplementary: true, manager: accountManager, rootPath: rootPath, auxiliaryMethods: accountAuxiliaryMethods, encryptionParameters: encryptionParameters)
|> mapToSignal { account -> Signal<Account?, NoError> in
if let account = account {
switch account {

View file

@ -5138,3 +5138,8 @@ Any member of this group will be able to see messages in the channel.";
"Appearance.TextSize.Apply" = "Set";
"Shortcut.SwitchAccount" = "Switch Account";
"Settings.Devices" = "Devices";
"Settings.AddDevice" = "Add";
"AuthSessions.DevicesTitle" = "Devices";
"AuthSessions.AddDevice" = "Add Device";

51
build-system/verify.sh Normal file
View file

@ -0,0 +1,51 @@
#!/bin/bash
export TELEGRAM_ENV_SET="1"
export DEVELOPMENT_CODE_SIGN_IDENTITY="iPhone Distribution: Digital Fortress LLC (C67CF9S4VU)"
export DISTRIBUTION_CODE_SIGN_IDENTITY="iPhone Distribution: Digital Fortress LLC (C67CF9S4VU)"
export DEVELOPMENT_TEAM="C67CF9S4VU"
export API_ID="8"
export API_HASH="7245de8e747a0d6fbe11f7cc14fcc0bb"
export BUNDLE_ID="ph.telegra.Telegraph"
export IS_INTERNAL_BUILD="false"
export IS_APPSTORE_BUILD="true"
export APPSTORE_ID="686449807"
export APP_SPECIFIC_URL_SCHEME="tgapp"
if [ -z "$BUILD_NUMBER" ]; then
echo "BUILD_NUMBER is not defined"
exit 1
fi
export ENTITLEMENTS_APP="Telegram-iOS/Telegram-iOS-AppStoreLLC.entitlements"
export DEVELOPMENT_PROVISIONING_PROFILE_APP="match Development ph.telegra.Telegraph"
export DISTRIBUTION_PROVISIONING_PROFILE_APP="match AppStore ph.telegra.Telegraph"
export ENTITLEMENTS_EXTENSION_SHARE="Share/Share-AppStoreLLC.entitlements"
export DEVELOPMENT_PROVISIONING_PROFILE_EXTENSION_SHARE="match Development ph.telegra.Telegraph.Share"
export DISTRIBUTION_PROVISIONING_PROFILE_EXTENSION_SHARE="match AppStore ph.telegra.Telegraph.Share"
export ENTITLEMENTS_EXTENSION_WIDGET="Widget/Widget-AppStoreLLC.entitlements"
export DEVELOPMENT_PROVISIONING_PROFILE_EXTENSION_WIDGET="match Development ph.telegra.Telegraph.Widget"
export DISTRIBUTION_PROVISIONING_PROFILE_EXTENSION_WIDGET="match AppStore ph.telegra.Telegraph.Widget"
export ENTITLEMENTS_EXTENSION_NOTIFICATIONSERVICE="NotificationService/NotificationService-AppStoreLLC.entitlements"
export DEVELOPMENT_PROVISIONING_PROFILE_EXTENSION_NOTIFICATIONSERVICE="match Development ph.telegra.Telegraph.NotificationService"
export DISTRIBUTION_PROVISIONING_PROFILE_EXTENSION_NOTIFICATIONSERVICE="match AppStore ph.telegra.Telegraph.NotificationService"
export ENTITLEMENTS_EXTENSION_NOTIFICATIONCONTENT="NotificationContent/NotificationContent-AppStoreLLC.entitlements"
export DEVELOPMENT_PROVISIONING_PROFILE_EXTENSION_NOTIFICATIONCONTENT="match Development ph.telegra.Telegraph.NotificationContent"
export DISTRIBUTION_PROVISIONING_PROFILE_EXTENSION_NOTIFICATIONCONTENT="match AppStore ph.telegra.Telegraph.NotificationContent"
export ENTITLEMENTS_EXTENSION_INTENTS="SiriIntents/SiriIntents-AppStoreLLC.entitlements"
export DEVELOPMENT_PROVISIONING_PROFILE_EXTENSION_INTENTS="match Development ph.telegra.Telegraph.SiriIntents"
export DISTRIBUTION_PROVISIONING_PROFILE_EXTENSION_INTENTS="match AppStore ph.telegra.Telegraph.SiriIntents"
export DEVELOPMENT_PROVISIONING_PROFILE_WATCH_APP="match Development ph.telegra.Telegraph.watchkitapp"
export DISTRIBUTION_PROVISIONING_PROFILE_WATCH_APP="match AppStore ph.telegra.Telegraph.watchkitapp"
export DEVELOPMENT_PROVISIONING_PROFILE_WATCH_EXTENSION="match Development ph.telegra.Telegraph.watchkitapp.watchkitextension"
export DISTRIBUTION_PROVISIONING_PROFILE_WATCH_EXTENSION="match AppStore ph.telegra.Telegraph.watchkitapp.watchkitextension"
BUILDBOX_DIR="buildbox"
export CODESIGNING_PROFILES_VARIANT="appstore"
export PACKAGE_METHOD="appstore"
$@

View file

@ -69,7 +69,14 @@ else
exit 1
fi
COMMIT_ID=$(git rev-parse HEAD)
COMMIT_COMMENT="$(git log -1 --pretty=%B)"
case "$COMMIT_COMMENT" in
*"[nocache]"*)
export BUCK_HTTP_CACHE=""
;;
esac
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)
@ -180,7 +187,7 @@ else
fi
scp -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -pr "$BUILDBOX_DIR/guest-build-telegram.sh" "$BUILDBOX_DIR/transient-data/source.tar" telegram@"$VM_IP":
ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null telegram@"$VM_IP" -o ServerAliveInterval=60 -t "export TELEGRAM_BUILD_APPSTORE_PASSWORD=\"$TELEGRAM_BUILD_APPSTORE_PASSWORD\"; export TELEGRAM_BUILD_APPSTORE_TEAM_NAME=\"$TELEGRAM_BUILD_APPSTORE_TEAM_NAME\"; export TELEGRAM_BUILD_APPSTORE_USERNAME=\"$TELEGRAM_BUILD_APPSTORE_USERNAME\"; export BUILD_NUMBER=\"$BUILD_NUMBER\"; export COMMIT_ID=\"$COMMIT_ID\"; export COMMIT_AUTHOR=\"$COMMIT_AUTHOR\"; export BUCK_HTTP_CACHE=\"$BUCK_HTTP_CACHE\"; $GUEST_SHELL -l guest-build-telegram.sh $BUILD_CONFIGURATION" || true
ssh -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null telegram@"$VM_IP" -o ServerAliveInterval=60 -t "export TELEGRAM_BUILD_APPSTORE_PASSWORD=\"$TELEGRAM_BUILD_APPSTORE_PASSWORD\"; export TELEGRAM_BUILD_APPSTORE_TEAM_NAME=\"$TELEGRAM_BUILD_APPSTORE_TEAM_NAME\"; export TELEGRAM_BUILD_APPSTORE_USERNAME=\"$TELEGRAM_BUILD_APPSTORE_USERNAME\"; export BUILD_NUMBER=\"$BUILD_NUMBER\"; export COMMIT_ID=\"$COMMIT_ID\"; export COMMIT_AUTHOR=\"$COMMIT_AUTHOR\"; export BUCK_HTTP_CACHE=\"$BUCK_HTTP_CACHE\"; export BUCK_DIR_CACHE=\"$BUCK_DIR_CACHE\"; export BUCK_CACHE_MODE=\"$BUCK_CACHE_MODE\"; $GUEST_SHELL -l guest-build-telegram.sh $BUILD_CONFIGURATION" || true
OUTPUT_PATH="build/artifacts"
rm -rf "$OUTPUT_PATH"

View file

@ -0,0 +1,2 @@
#!/bin/bash

View file

@ -1,5 +1,7 @@
#!/bin/sh
set -x
if [ -z "BUILD_NUMBER" ]; then
echo "BUILD_NUMBER is not set"
exit 1
@ -11,15 +13,12 @@ if [ -z "COMMIT_ID" ]; then
fi
if [ "$1" == "hockeyapp" ] || [ "$1" == "testinghockeyapp" ]; then
FASTLANE_BUILD_CONFIGURATION="internalhockeyapp"
CERTS_PATH="codesigning_data/certs"
PROFILES_PATH="codesigning_data/profiles"
CERTS_PATH="$HOME/codesigning_data/certs"
PROFILES_PATH="$HOME/codesigning_data/profiles"
elif [ "$1" == "testinghockeyapp-local" ]; then
FASTLANE_BUILD_CONFIGURATION="testinghockeyapp"
CERTS_PATH="codesigning_data/certs"
PROFILES_PATH="codesigning_data/profiles"
CERTS_PATH="$HOME/codesigning_data/certs"
PROFILES_PATH="$HOME/codesigning_data/profiles"
elif [ "$1" == "appstore" ]; then
FASTLANE_BUILD_CONFIGURATION="testflight_llc"
if [ -z "$TELEGRAM_BUILD_APPSTORE_PASSWORD" ]; then
echo "TELEGRAM_BUILD_APPSTORE_PASSWORD is not set"
exit 1
@ -31,16 +30,11 @@ elif [ "$1" == "appstore" ]; then
FASTLANE_ITC_USERNAME="$TELEGRAM_BUILD_APPSTORE_USERNAME"
FASTLANE_PASSWORD="$TELEGRAM_BUILD_APPSTORE_PASSWORD"
FASTLANE_ITC_TEAM_NAME="$TELEGRAM_BUILD_APPSTORE_TEAM_NAME"
CERTS_PATH="codesigning_data/certs"
PROFILES_PATH="codesigning_data/profiles"
CERTS_PATH="$HOME/codesigning_data/certs"
PROFILES_PATH="$HOME/codesigning_data/profiles"
elif [ "$1" == "verify" ]; then
FASTLANE_BUILD_CONFIGURATION="build_for_appstore"
CERTS_PATH="codesigning_data/certs"
PROFILES_PATH="codesigning_data/profiles"
elif [ "$1" == "verify-local" ]; then
FASTLANE_BUILD_CONFIGURATION="build_for_appstore"
CERTS_PATH="buildbox/fake-codesigning/certs"
PROFILES_PATH="buildbox/fake-codesigning/profiles"
CERTS_PATH="build-system/fake-codesigning/certs/distribution"
PROFILES_PATH="build-system/fake-codesigning/profiles"
else
echo "Unknown configuration $1"
exit 1
@ -57,31 +51,16 @@ security list-keychains -d user -s "$MY_KEYCHAIN" $(security list-keychains -d u
security set-keychain-settings "$MY_KEYCHAIN"
security unlock-keychain -p "$MY_KEYCHAIN_PASSWORD" "$MY_KEYCHAIN"
for f in $(ls "$CERTS_PATH"); do
fastlane run import_certificate "certificate_path:$CERTS_PATH/$f" keychain_name:"$MY_KEYCHAIN" keychain_password:"$MY_KEYCHAIN_PASSWORD" log_output:true
done
SOURCE_PATH="telegram-ios"
mkdir -p "$HOME/Library/MobileDevice/Provisioning Profiles"
for f in $(ls "$PROFILES_PATH"); do
PROFILE_PATH="$PROFILES_PATH/$f"
uuid=`grep UUID -A1 -a "$PROFILE_PATH" | grep -io "[-A-F0-9]\{36\}"`
cp -f "$PROFILE_PATH" "$HOME/Library/MobileDevice/Provisioning Profiles/$uuid.mobileprovision"
done
if [ "$1" == "verify-local" ]; then
fastlane "$FASTLANE_BUILD_CONFIGURATION"
else
SOURCE_PATH="telegram-ios"
if [ -d "$SOURCE_PATH" ]; then
echo "Directory $SOURCE_PATH should not exist"
exit 1
fi
mkdir "$SOURCE_PATH"
if [ -d "$SOURCE_PATH" ]; then
echo "Directory $SOURCE_PATH should not exist"
exit 1
fi
mkdir "$SOURCE_PATH"
if [ "$1" != "verify" ]; then
SIZE_IN_BLOCKS=$((12*1024*1024*1024/512))
DEV=`hdid -nomount ram://$SIZE_IN_BLOCKS`
@ -95,38 +74,58 @@ else
echo "Error creating ramdisk"
exit 1
fi
echo "Unpacking files..."
mkdir -p "$SOURCE_PATH/buildbox"
mkdir -p "$SOURCE_PATH/buildbox/transient-data"
cp -r "$HOME/codesigning_teams" "$SOURCE_PATH/buildbox/transient-data/teams"
BASE_DIR=$(pwd)
cd "$SOURCE_PATH"
tar -xf "../source.tar"
if [ "$1" == "hockeyapp" ]; then
BUILD_ENV_SCRIPT="internal"
FASTLANE_BUILD_CONFIGURATION="internalhockeyapp"
APP_TARGET="app_arm64"
elif [ "$1" == "appstore" ]; then
BUILD_ENV_SCRIPT="appstore"
FASTLANE_BUILD_CONFIGURATION="testflight_llc"
APP_TARGET="app"
else
echo "Unsupported configuration $1"
exit 1
fi
BUCK="$(pwd)/tools/buck" BUCK_HTTP_CACHE="$BUCK_HTTP_CACHE" LOCAL_CODESIGNING=1 sh "../telegram-ios-shared/buildbox/bin/$BUILD_ENV_SCRIPT.sh" make "$APP_TARGET"
OUTPUT_PATH="build/artifacts"
rm -rf "$OUTPUT_PATH"
mkdir -p "$OUTPUT_PATH"
cp "build/Telegram_signed.ipa" "./$OUTPUT_PATH/Telegram.ipa"
cp "build/DSYMs.zip" "./$OUTPUT_PATH/Telegram.DSYMs.zip"
cd "$BASE_DIR"
fi
echo "Unpacking files..."
mkdir -p "$SOURCE_PATH/buildbox"
mkdir -p "$SOURCE_PATH/buildbox/transient-data"
cp -r "$HOME/codesigning_teams" "$SOURCE_PATH/buildbox/transient-data/teams"
BASE_DIR=$(pwd)
cd "$SOURCE_PATH"
tar -xf "../source.tar"
for f in $(ls "$CERTS_PATH"); do
"$HOME/.fastlane/bin/fastlane" run import_certificate "certificate_path:$CERTS_PATH/$f" keychain_name:"$MY_KEYCHAIN" keychain_password:"$MY_KEYCHAIN_PASSWORD" log_output:true
done
mkdir -p "$HOME/Library/MobileDevice/Provisioning Profiles"
for f in $(ls "$PROFILES_PATH"); do
PROFILE_PATH="$PROFILES_PATH/$f"
uuid=`grep UUID -A1 -a "$PROFILE_PATH" | grep -io "[-A-F0-9]\{36\}"`
cp -f "$PROFILE_PATH" "$HOME/Library/MobileDevice/Provisioning Profiles/$uuid.mobileprovision"
done
if [ "$1" == "hockeyapp" ]; then
BUILD_ENV_SCRIPT="../telegram-ios-shared/buildbox/bin/internal.sh"
APP_TARGET="app_arm64"
elif [ "$1" == "appstore" ]; then
BUILD_ENV_SCRIPT="../telegram-ios-shared/buildbox/bin/appstore.sh"
APP_TARGET="app"
elif [ "$1" == "verify" ]; then
BUILD_ENV_SCRIPT="build-system/verify.sh"
APP_TARGET="app"
export CODESIGNING_DATA_PATH="build-system/fake-codesigning"
export CODESIGNING_CERTS_VARIANT="distribution"
export CODESIGNING_PROFILES_VARIANT="appstore"
else
echo "Unsupported configuration $1"
exit 1
fi
if [ -d "$BUCK_DIR_CACHE" ]; then
sudo chown telegram "$BUCK_DIR_CACHE"
fi
BUCK="$(pwd)/tools/buck" BUCK_HTTP_CACHE="$BUCK_HTTP_CACHE" BUCK_CACHE_MODE="$BUCK_CACHE_MODE" BUCK_DIR_CACHE="$BUCK_DIR_CACHE" LOCAL_CODESIGNING=1 sh "$BUILD_ENV_SCRIPT" make "$APP_TARGET"
OUTPUT_PATH="build/artifacts"
rm -rf "$OUTPUT_PATH"
mkdir -p "$OUTPUT_PATH"
cp "build/Telegram_signed.ipa" "./$OUTPUT_PATH/Telegram.ipa"
cp "build/DSYMs.zip" "./$OUTPUT_PATH/Telegram.DSYMs.zip"
cd "$BASE_DIR"

View file

@ -0,0 +1,53 @@
#!/bin/bash
set -e
set -x
CONFIGURATION="$1"
MODE="$2"
if [ -z "$CONFIGURATION" ] || [ -z "$MODE" ] ; then
echo "Usage: sh deploy-telegram.sh CONFIGURATION [cached|full]"
exit 1
fi
if [ "$MODE" == "cached" ]; then
BUCK_HTTP_CACHE="$BUCK_HTTP_CACHE"
ERROR_OUTPUT_PATH="build/verifysanity_artifacts"
elif [ "$MODE" == "full" ]; then
BUCK_HTTP_CACHE=""
ERROR_OUTPUT_PATH="build/verify_artifacts"
else
echo "Unknown mode $MODE"
exit 1
fi
OUTPUT_PATH="build/artifacts"
if [ "$CONFIGURATION" == "appstore" ]; then
IPA_PATH="$OUTPUT_PATH/Telegram.ipa"
else
echo "Unknown configuration $CONFIGURATION"
exit 1
fi
if [ ! -f "$IPA_PATH" ]; then
echo "$IPA_PATH not found"
exit 1
fi
VERIFY_PATH="TelegramVerifyBuild.ipa"
mv "$IPA_PATH" "$VERIFY_PATH"
BUCK_HTTP_CACHE="$BUCK_HTTP_CACHE" sh buildbox/build-telegram.sh verify
python3 tools/ipadiff.py "$IPA_PATH" "$VERIFY_PATH"
retVal=$?
if [ $retVal -ne 0 ]; then
mkdir -p "$ERROR_OUTPUT_PATH"
cp "$IPA_PATH" "$ERROR_OUTPUT_PATH"/
exit 1
fi

View file

@ -0,0 +1,28 @@
load("//Config:buck_rule_macros.bzl", "static_library")
static_library(
name = "AuthTransferUI",
srcs = glob([
"Sources/**/*.swift",
]),
deps = [
"//submodules/TelegramCore:TelegramCore#shared",
"//submodules/SyncCore:SyncCore#shared",
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit#shared",
"//submodules/AsyncDisplayKit:AsyncDisplayKit#shared",
"//submodules/Display:Display#shared",
"//submodules/TelegramPresentationData:TelegramPresentationData",
"//submodules/AccountContext:AccountContext",
"//submodules/QrCode:QrCode",
"//submodules/Camera:Camera",
"//submodules/GlassButtonNode:GlassButtonNode",
"//submodules/AlertUI:AlertUI",
"//submodules/AppBundle:AppBundle",
"//submodules/SolidRoundedButtonNode:SolidRoundedButtonNode",
"//submodules/OverlayStatusController:OverlayStatusController",
"//submodules/AnimatedStickerNode:AnimatedStickerNode",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",
],
)

View file

@ -0,0 +1,166 @@
import Foundation
import UIKit
import AppBundle
import AsyncDisplayKit
import Display
import SolidRoundedButtonNode
import SwiftSignalKit
import OverlayStatusController
import AnimatedStickerNode
import TelegramPresentationData
import TelegramCore
import AccountContext
final class AuthTransferConfirmationNode: ASDisplayNode {
private let context: AccountContext
private var presentationData: PresentationData
private let tokenInfo: AuthTransferTokenInfo
private let containerNode: ASDisplayNode
private let backgroundNode: ASImageNode
private let iconNode: ASImageNode
private let titleNode: ImmediateTextNode
private let appNameNode: ImmediateTextNode
private let locationInfoNode: ImmediateTextNode
private let acceptButtonNode: SolidRoundedButtonNode
private let cancelButtonNode: SolidRoundedButtonNode
private var validLayout: (ContainerViewLayout, CGFloat)?
init(context: AccountContext, presentationData: PresentationData, tokenInfo: AuthTransferTokenInfo, accept: @escaping () -> Void, cancel: @escaping () -> Void) {
self.context = context
self.presentationData = presentationData
self.tokenInfo = tokenInfo
self.containerNode = ASDisplayNode()
self.backgroundNode = ASImageNode()
self.backgroundNode.displayWithoutProcessing = true
self.backgroundNode.displaysAsynchronously = false
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 24.0, color: self.presentationData.theme.list.plainBackgroundColor)
self.iconNode = ASImageNode()
self.iconNode.displayWithoutProcessing = true
self.iconNode.displaysAsynchronously = false
self.iconNode.image = UIImage(bundleImageName: "Settings/TransferAuthLaptop")
self.titleNode = ImmediateTextNode()
self.titleNode.textAlignment = .center
self.titleNode.maximumNumberOfLines = 2
self.appNameNode = ImmediateTextNode()
self.appNameNode.textAlignment = .center
self.appNameNode.maximumNumberOfLines = 2
self.locationInfoNode = ImmediateTextNode()
self.locationInfoNode.textAlignment = .center
self.locationInfoNode.maximumNumberOfLines = 0
self.acceptButtonNode = SolidRoundedButtonNode(title: "Confirm Log In", icon: nil, theme: SolidRoundedButtonTheme(backgroundColor: self.presentationData.theme.list.itemDestructiveColor, foregroundColor: self.presentationData.theme.list.itemCheckColors.foregroundColor), height: 50.0, cornerRadius: 10.0, gloss: false)
self.cancelButtonNode = SolidRoundedButtonNode(title: self.presentationData.strings.Common_Cancel, icon: nil, theme: SolidRoundedButtonTheme(backgroundColor: self.presentationData.theme.list.itemCheckColors.fillColor, foregroundColor: self.presentationData.theme.list.itemCheckColors.foregroundColor), height: 50.0, cornerRadius: 10.0, gloss: false)
super.init()
self.addSubnode(self.containerNode)
self.containerNode.addSubnode(self.backgroundNode)
self.containerNode.addSubnode(self.iconNode)
self.containerNode.addSubnode(self.titleNode)
self.containerNode.addSubnode(self.appNameNode)
self.containerNode.addSubnode(self.locationInfoNode)
self.containerNode.addSubnode(self.acceptButtonNode)
self.containerNode.addSubnode(self.cancelButtonNode)
let titleFont = Font.bold(24.0)
let subtitleFont = Font.regular(16.0)
let textColor = self.presentationData.theme.list.itemPrimaryTextColor
let seccondaryTextColor = self.presentationData.theme.list.itemSecondaryTextColor
self.titleNode.attributedText = NSAttributedString(string: "\(tokenInfo.appName)", font: titleFont, textColor: textColor)
self.appNameNode.attributedText = NSAttributedString(string: "\(tokenInfo.deviceModel), \(tokenInfo.platform) \(tokenInfo.systemVersion)", font: subtitleFont, textColor: seccondaryTextColor)
self.locationInfoNode.attributedText = NSAttributedString(string: "\(tokenInfo.region)\nIP: \(tokenInfo.ip)", font: subtitleFont, textColor: seccondaryTextColor)
self.acceptButtonNode.pressed = { [weak self] in
accept()
}
self.cancelButtonNode.pressed = {
cancel()
}
}
override func didLoad() {
super.didLoad()
}
func animateIn() {
self.containerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: self.containerNode.bounds.height), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
}
func animateOut(completion: @escaping () -> Void) {
self.containerNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: self.containerNode.bounds.height), duration: 0.3, removeOnCompletion: false, additive: true, completion: { _ in
completion()
})
}
func updateLayout(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
var insets = layout.insets(options: [])
let sideInset: CGFloat = 22.0
let buttonSideInset: CGFloat = 16.0
let bottomInset = insets.bottom + 10.0
let buttonWidth = layout.size.width - buttonSideInset * 2.0
let buttonHeight: CGFloat = 50.0
let buttonSpacing: CGFloat = 20.0
let contentButtonSpacing: CGFloat = 35.0
let titleSpacing: CGFloat = 1.0
let locationSpacing: CGFloat = 35.0
let iconSpacing: CGFloat = 35.0
let topInset: CGFloat = 35.0
let iconSize = self.iconNode.image?.size ?? CGSize(width: 10.0, height: 1.0)
let titleSize = self.titleNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: .greatestFiniteMagnitude))
let appNameSize = self.appNameNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: .greatestFiniteMagnitude))
let locationSize = self.locationInfoNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: .greatestFiniteMagnitude))
var contentHeight: CGFloat = 0.0
contentHeight += topInset + iconSize.height
contentHeight += iconSpacing + titleSize.height
contentHeight += titleSpacing + appNameSize.height
contentHeight += locationSpacing + locationSize.height
contentHeight += contentButtonSpacing + bottomInset + buttonHeight + buttonSpacing + buttonHeight
let iconFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - iconSize.width) / 2.0), y: topInset), size: iconSize)
transition.updateFrame(node: self.iconNode, frame: iconFrame)
let titleFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - titleSize.width) / 2.0), y: iconFrame.maxY + iconSpacing), size: titleSize)
transition.updateFrame(node: self.titleNode, frame: titleFrame)
let appNameFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - appNameSize.width) / 2.0), y: titleFrame.maxY + titleSpacing), size: appNameSize)
transition.updateFrame(node: self.appNameNode, frame: appNameFrame)
let locationFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - locationSize.width) / 2.0), y: appNameFrame.maxY + locationSpacing), size: locationSize)
transition.updateFrame(node: self.locationInfoNode, frame: locationFrame)
let cancelButtonFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - buttonWidth) / 2.0), y: contentHeight - bottomInset - buttonHeight), size: CGSize(width: buttonWidth, height: buttonHeight))
transition.updateFrame(node: self.cancelButtonNode, frame: cancelButtonFrame)
self.cancelButtonNode.updateLayout(width: cancelButtonFrame.width, transition: transition)
let acceptButtonFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - buttonWidth) / 2.0), y: cancelButtonFrame.minY - buttonSpacing - buttonHeight), size: CGSize(width: buttonWidth, height: buttonHeight))
transition.updateFrame(node: self.acceptButtonNode, frame: acceptButtonFrame)
self.acceptButtonNode.updateLayout(width: acceptButtonFrame.width, transition: transition)
transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - contentHeight), size: CGSize(width: layout.size.width, height: contentHeight)))
transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: contentHeight + 24.0)))
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let result = self.cancelButtonNode.view.hitTest(self.view.convert(point, to: self.cancelButtonNode.view), with: event) {
return result
}
if let result = self.acceptButtonNode.view.hitTest(self.view.convert(point, to: self.acceptButtonNode.view), with: event) {
return result
}
return super.hitTest(point, with: event)
}
}

View file

@ -0,0 +1,448 @@
import Foundation
import UIKit
import AccountContext
import AsyncDisplayKit
import Display
import SwiftSignalKit
import Camera
import GlassButtonNode
import CoreImage
import AlertUI
import TelegramPresentationData
import TelegramCore
private func parseAuthTransferUrl(_ url: URL) -> Data? {
var tokenString: String?
if let query = url.query, let components = URLComponents(string: "/?" + query), let queryItems = components.queryItems {
for queryItem in queryItems {
if let value = queryItem.value {
if queryItem.name == "token", !value.isEmpty {
tokenString = value
}
}
}
}
if var tokenString = tokenString {
tokenString = tokenString.replacingOccurrences(of: "-", with: "+")
tokenString = tokenString.replacingOccurrences(of: "_", with: "/")
while tokenString.count % 4 != 0 {
tokenString.append("=")
}
if let data = Data(base64Encoded: tokenString) {
return data
}
}
return nil
}
private func generateFrameImage() -> UIImage? {
return generateImage(CGSize(width: 64.0, height: 64.0), contextGenerator: { size, context in
let bounds = CGRect(origin: CGPoint(), size: size)
context.clear(bounds)
context.setStrokeColor(UIColor.white.cgColor)
context.setLineWidth(4.0)
context.setLineCap(.round)
var path = CGMutablePath();
path.move(to: CGPoint(x: 2.0, y: 2.0 + 26.0))
path.addArc(tangent1End: CGPoint(x: 2.0, y: 2.0), tangent2End: CGPoint(x: 2.0 + 26.0, y: 2.0), radius: 6.0)
path.addLine(to: CGPoint(x: 2.0 + 26.0, y: 2.0))
context.addPath(path)
context.strokePath()
path.move(to: CGPoint(x: size.width - 2.0, y: 2.0 + 26.0))
path.addArc(tangent1End: CGPoint(x: size.width - 2.0, y: 2.0), tangent2End: CGPoint(x: 2.0 + 26.0, y: 2.0), radius: 6.0)
path.addLine(to: CGPoint(x: size.width - 2.0 - 26.0, y: 2.0))
context.addPath(path)
context.strokePath()
path.move(to: CGPoint(x: 2.0, y: size.height - 2.0 - 26.0))
path.addArc(tangent1End: CGPoint(x: 2.0, y: size.height - 2.0), tangent2End: CGPoint(x: 2.0 + 26.0, y: size.height - 2.0), radius: 6.0)
path.addLine(to: CGPoint(x: 2.0 + 26.0, y: size.height - 2.0))
context.addPath(path)
context.strokePath()
path.move(to: CGPoint(x: size.width - 2.0, y: size.height - 2.0 - 26.0))
path.addArc(tangent1End: CGPoint(x: size.width - 2.0, y: size.height - 2.0), tangent2End: CGPoint(x: 2.0 + 26.0, y: size.height - 2.0), radius: 6.0)
path.addLine(to: CGPoint(x: size.width - 2.0 - 26.0, y: size.height - 2.0))
context.addPath(path)
context.strokePath()
})?.stretchableImage(withLeftCapWidth: 32, topCapHeight: 32)
}
public final class AuthTransferScanScreen: ViewController {
private let context: AccountContext
private let activeSessionsContext: ActiveSessionsContext?
private var presentationData: PresentationData
private var codeDisposable: Disposable?
private var inForegroundDisposable: Disposable?
private let approveDisposable = MetaDisposable()
public init(context: AccountContext, activeSessionsContext: ActiveSessionsContext?) {
self.context = context
self.activeSessionsContext = activeSessionsContext
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
let navigationBarTheme = NavigationBarTheme(buttonColor: .white, disabledButtonColor: .white, primaryTextColor: .white, backgroundColor: .clear, separatorColor: .clear, badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear)
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: navigationBarTheme, strings: NavigationBarStrings(back: self.presentationData.strings.Common_Back, close: self.presentationData.strings.Common_Close)))
self.statusBar.statusBarStyle = .White
self.navigationPresentation = .modalInLargeLayout
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .portrait)
self.navigationBar?.intrinsicCanTransitionInline = false
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Wallet_Navigation_Back, style: .plain, target: nil, action: nil)
self.inForegroundDisposable = (context.sharedContext.applicationBindings.applicationInForeground
|> deliverOnMainQueue).start(next: { [weak self] inForeground in
guard let strongSelf = self else {
return
}
(strongSelf.displayNode as! AuthTransferScanScreenNode).updateInForeground(inForeground)
})
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
deinit {
self.codeDisposable?.dispose()
self.inForegroundDisposable?.dispose()
self.approveDisposable.dispose()
}
@objc private func backPressed() {
self.dismiss()
}
override public func loadDisplayNode() {
self.displayNode = AuthTransferScanScreenNode(presentationData: self.presentationData)
self.displayNodeDidLoad()
self.codeDisposable = ((self.displayNode as! AuthTransferScanScreenNode).focusedCode.get()
|> map { code -> String? in
return code?.message
}
|> distinctUntilChanged
|> mapToSignal { code -> Signal<String?, NoError> in
return .single(code)
|> delay(0.5, queue: Queue.mainQueue())
}).start(next: { [weak self] code in
guard let strongSelf = self, let code = code else {
return
}
if let url = URL(string: code), let parsedToken = parseAuthTransferUrl(url) {
let _ = (getAuthTransferTokenInfo(network: strongSelf.context.account.network, token: parsedToken)
|> deliverOnMainQueue).start(next: { tokenInfo in
guard let strongSelf = self else {
return
}
(strongSelf.displayNode as! AuthTransferScanScreenNode).updateTokenPreview(confirmationNode: AuthTransferConfirmationNode(context: strongSelf.context, presentationData: strongSelf.presentationData, tokenInfo: tokenInfo, accept: {
guard let strongSelf = self else {
return
}
strongSelf.approveDisposable.set((approveAuthTransferToken(account: strongSelf.context.account, token: parsedToken)
|> deliverOnMainQueue).start(error: { _ in
guard let strongSelf = self else {
return
}
(strongSelf.displayNode as! AuthTransferScanScreenNode).updateTokenPreview(confirmationNode: nil)
}, completed: {
guard let strongSelf = self else {
return
}
let activeSessionsContext = strongSelf.activeSessionsContext
Queue.mainQueue().after(1.5, {
activeSessionsContext?.loadMore()
})
strongSelf.dismiss()
}))
}, cancel: {
guard let strongSelf = self else {
return
}
(strongSelf.displayNode as! AuthTransferScanScreenNode).updateTokenPreview(confirmationNode: nil)
}))
})
}
})
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
(self.displayNode as! AuthTransferScanScreenNode).containerLayoutUpdated(layout: layout, navigationHeight: self.navigationHeight, transition: transition)
}
}
private final class AuthTransferScanScreenNode: ViewControllerTracingNode, UIScrollViewDelegate {
private var presentationData: PresentationData
private let previewNode: CameraPreviewNode
private let fadeNode: ASDisplayNode
private let topDimNode: ASDisplayNode
private let bottomDimNode: ASDisplayNode
private let leftDimNode: ASDisplayNode
private let rightDimNode: ASDisplayNode
private let centerDimNode: ASDisplayNode
private let frameNode: ASImageNode
private let torchButtonNode: GlassButtonNode
private let titleNode: ImmediateTextNode
private let textNode: ImmediateTextNode
private let descriptionNode: ImmediateTextNode
private let camera: Camera
private let codeDisposable = MetaDisposable()
fileprivate let focusedCode = ValuePromise<CameraCode?>(ignoreRepeated: true)
private var focusedRect: CGRect?
private(set) var confirmationNode: AuthTransferConfirmationNode?
private var validLayout: (ContainerViewLayout, CGFloat)?
init(presentationData: PresentationData) {
self.presentationData = presentationData
self.previewNode = CameraPreviewNode()
self.previewNode.backgroundColor = .black
self.fadeNode = ASDisplayNode()
self.fadeNode.alpha = 0.0
self.fadeNode.backgroundColor = .black
self.topDimNode = ASDisplayNode()
self.topDimNode.alpha = 0.625
self.topDimNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.8)
self.bottomDimNode = ASDisplayNode()
self.bottomDimNode.alpha = 0.625
self.bottomDimNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.8)
self.leftDimNode = ASDisplayNode()
self.leftDimNode.alpha = 0.625
self.leftDimNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.8)
self.rightDimNode = ASDisplayNode()
self.rightDimNode.alpha = 0.625
self.rightDimNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.8)
self.centerDimNode = ASDisplayNode()
self.centerDimNode.alpha = 0.0
self.centerDimNode.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.8)
self.frameNode = ASImageNode()
self.frameNode.image = generateFrameImage()
self.torchButtonNode = GlassButtonNode(icon: UIImage(bundleImageName: "Wallet/CameraFlashIcon")!, label: nil)
self.titleNode = ImmediateTextNode()
self.titleNode.displaysAsynchronously = false
self.titleNode.attributedText = NSAttributedString(string: presentationData.strings.Wallet_Qr_ScanCode, font: Font.bold(32.0), textColor: .white)
self.titleNode.maximumNumberOfLines = 0
self.titleNode.textAlignment = .center
self.textNode = ImmediateTextNode()
self.textNode.displaysAsynchronously = false
self.textNode.attributedText = NSAttributedString(string: "Scan a QR code to log into\nthis account on another device.", font: Font.regular(16.0), textColor: .white)
self.textNode.maximumNumberOfLines = 0
self.textNode.textAlignment = .center
self.descriptionNode = ImmediateTextNode()
self.descriptionNode.displaysAsynchronously = false
self.descriptionNode.attributedText = NSAttributedString(string: "Telegram is available for\niPhone, iPad, macOS, Windows and Linux", font: Font.regular(14.0), textColor: .white)
self.descriptionNode.maximumNumberOfLines = 0
self.descriptionNode.textAlignment = .center
self.camera = Camera(configuration: .init(preset: .hd1920x1080, position: .back, audio: false))
super.init()
self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
self.addSubnode(self.previewNode)
self.addSubnode(self.fadeNode)
self.addSubnode(self.topDimNode)
self.addSubnode(self.bottomDimNode)
self.addSubnode(self.leftDimNode)
self.addSubnode(self.rightDimNode)
self.addSubnode(self.centerDimNode)
self.addSubnode(self.frameNode)
//self.addSubnode(self.torchButtonNode)
self.addSubnode(self.titleNode)
self.addSubnode(self.textNode)
self.addSubnode(self.descriptionNode)
self.torchButtonNode.addTarget(self, action: #selector(self.torchPressed), forControlEvents: .touchUpInside)
}
deinit {
self.codeDisposable.dispose()
self.camera.stopCapture(invalidate: true)
}
fileprivate func updateInForeground(_ inForeground: Bool) {
if !inForeground {
self.camera.stopCapture(invalidate: false)
} else {
self.camera.startCapture()
}
}
override func didLoad() {
super.didLoad()
self.camera.attachPreviewNode(self.previewNode)
self.camera.startCapture()
let throttledSignal = self.camera.detectedCodes
|> mapToThrottled { next -> Signal<[CameraCode], NoError> in
return .single(next) |> then(.complete() |> delay(0.3, queue: Queue.concurrentDefaultQueue()))
}
self.codeDisposable.set((throttledSignal
|> deliverOnMainQueue).start(next: { [weak self] codes in
guard let strongSelf = self else {
return
}
let filteredCodes = codes.filter { $0.message.hasPrefix("tg://") }
if let code = filteredCodes.first, CGRect(x: 0.3, y: 0.3, width: 0.4, height: 0.4).contains(code.boundingBox.center) {
strongSelf.focusedCode.set(code)
strongSelf.updateFocusedRect(code.boundingBox)
} else {
strongSelf.focusedCode.set(nil)
strongSelf.updateFocusedRect(nil)
}
}))
}
private func updateFocusedRect(_ rect: CGRect?) {
self.focusedRect = rect
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.4, curve: .spring))
}
}
func updateTokenPreview(confirmationNode: AuthTransferConfirmationNode?) {
if let confirmationNode = self.confirmationNode {
confirmationNode.animateOut { [weak confirmationNode] in
confirmationNode?.removeFromSupernode()
}
self.confirmationNode = nil
}
self.confirmationNode = confirmationNode
if let confirmationNode = self.confirmationNode {
self.addSubnode(confirmationNode)
if let (layout, navigationHeight) = self.validLayout {
confirmationNode.updateLayout(layout: layout, transition: .immediate)
confirmationNode.animateIn()
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
}
} else {
if let (layout, navigationHeight) = self.validLayout {
self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .animated(duration: 0.3, curve: .easeInOut))
}
}
}
func containerLayoutUpdated(layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) {
self.validLayout = (layout, navigationHeight)
let sideInset: CGFloat = 66.0
let titleSpacing: CGFloat = 48.0
let bounds = CGRect(origin: CGPoint(), size: layout.size)
if case .tablet = layout.deviceMetrics.type {
if UIDevice.current.orientation == .landscapeLeft {
self.previewNode.transform = CATransform3DMakeRotation(-CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
} else if UIDevice.current.orientation == .landscapeRight {
self.previewNode.transform = CATransform3DMakeRotation(CGFloat.pi / 2.0, 0.0, 0.0, 1.0)
} else {
self.previewNode.transform = CATransform3DIdentity
}
}
transition.updateFrame(node: self.previewNode, frame: bounds)
transition.updateFrame(node: self.fadeNode, frame: bounds)
let frameSide = max(240.0, layout.size.width - sideInset * 2.0)
let dimHeight = ceil((layout.size.height - frameSide) / 2.0)
let dimInset = (layout.size.width - frameSide) / 2.0
let dimAlpha: CGFloat
let dimRect: CGRect
let controlsAlpha: CGFloat
var centerDimAlpha: CGFloat = 0.0
var frameAlpha: CGFloat = 1.0
if self.confirmationNode != nil {
controlsAlpha = 0.0
dimAlpha = 0.625
centerDimAlpha = 0.625
frameAlpha = 0.0
if let focusedRect = self.focusedRect {
let side = max(bounds.width * focusedRect.width, bounds.height * focusedRect.height) * 0.6
let center = CGPoint(x: (1.0 - focusedRect.center.y) * bounds.width, y: focusedRect.center.x * bounds.height)
dimRect = CGRect(x: center.x - side / 2.0, y: center.y - side / 2.0, width: side, height: side)
} else {
dimRect = CGRect(x: dimInset, y: dimHeight, width: layout.size.width - dimInset * 2.0, height: layout.size.height - dimHeight * 2.0)
}
} else if let focusedRect = self.focusedRect {
controlsAlpha = 0.0
dimAlpha = 1.0
let side = max(bounds.width * focusedRect.width, bounds.height * focusedRect.height) * 0.6
let center = CGPoint(x: (1.0 - focusedRect.center.y) * bounds.width, y: focusedRect.center.x * bounds.height)
dimRect = CGRect(x: center.x - side / 2.0, y: center.y - side / 2.0, width: side, height: side)
} else {
controlsAlpha = 1.0
dimAlpha = 0.625
dimRect = CGRect(x: dimInset, y: dimHeight, width: layout.size.width - dimInset * 2.0, height: layout.size.height - dimHeight * 2.0)
}
transition.updateAlpha(node: self.topDimNode, alpha: dimAlpha)
transition.updateAlpha(node: self.bottomDimNode, alpha: dimAlpha)
transition.updateAlpha(node: self.leftDimNode, alpha: dimAlpha)
transition.updateAlpha(node: self.rightDimNode, alpha: dimAlpha)
transition.updateAlpha(node: self.centerDimNode, alpha: centerDimAlpha)
transition.updateAlpha(node: self.frameNode, alpha: frameAlpha)
transition.updateFrame(node: self.topDimNode, frame: CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: dimRect.minY))
transition.updateFrame(node: self.bottomDimNode, frame: CGRect(x: 0.0, y: dimRect.maxY, width: layout.size.width, height: max(0.0, layout.size.height - dimRect.maxY)))
transition.updateFrame(node: self.leftDimNode, frame: CGRect(x: 0.0, y: dimRect.minY, width: max(0.0, dimRect.minX), height: dimRect.height))
transition.updateFrame(node: self.rightDimNode, frame: CGRect(x: dimRect.maxX, y: dimRect.minY, width: max(0.0, layout.size.width - dimRect.maxX), height: dimRect.height))
transition.updateFrame(node: self.frameNode, frame: dimRect.insetBy(dx: -2.0, dy: -2.0))
transition.updateFrame(node: self.centerDimNode, frame: dimRect)
let buttonSize = CGSize(width: 72.0, height: 72.0)
transition.updateFrame(node: self.torchButtonNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - buttonSize.width) / 2.0), y: dimHeight + frameSide + 50.0), size: buttonSize))
transition.updateAlpha(node: self.titleNode, alpha: controlsAlpha)
transition.updateAlpha(node: self.textNode, alpha: controlsAlpha)
transition.updateAlpha(node: self.descriptionNode, alpha: controlsAlpha)
transition.updateAlpha(node: self.torchButtonNode, alpha: controlsAlpha)
let titleSize = self.titleNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: layout.size.height))
let textSize = self.textNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: layout.size.height))
let descriptionSize = self.descriptionNode.updateLayout(CGSize(width: layout.size.width - sideInset * 2.0, height: layout.size.height))
let textFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - textSize.width) / 2.0), y: dimHeight - textSize.height - titleSpacing), size: textSize)
let titleFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - titleSize.width) / 2.0), y: textFrame.minY - 18.0 - titleSize.height), size: titleSize)
let descriptionFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - descriptionSize.width) / 2.0), y: layout.size.height - dimHeight + titleSpacing), size: descriptionSize)
transition.updateFrameAdditive(node: self.titleNode, frame: titleFrame)
transition.updateFrameAdditive(node: self.textNode, frame: textFrame)
transition.updateFrameAdditive(node: self.descriptionNode, frame: descriptionFrame)
if let confirmationNode = self.confirmationNode {
confirmationNode.updateLayout(layout: layout, transition: transition)
}
}
@objc private func torchPressed() {
self.torchButtonNode.isSelected = !self.torchButtonNode.isSelected
self.camera.setTorchActive(self.torchButtonNode.isSelected)
}
}

View file

@ -313,6 +313,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
let avatarNode: AvatarNode
let titleNode: TextNode
let authorNode: TextNode
let measureNode: TextNode
private var currentItemHeight: CGFloat?
let textNode: TextNode
let contentImageNode: TransformImageNode
let inputActivitiesNode: ChatListInputActivitiesNode
@ -431,6 +433,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
self.contextContainer = ContextControllerSourceNode()
self.measureNode = TextNode()
self.titleNode = TextNode()
self.titleNode.isUserInteractionEnabled = false
self.titleNode.displaysAsynchronously = true
@ -633,6 +637,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
let textLayout = TextNode.asyncLayout(self.textNode)
let titleLayout = TextNode.asyncLayout(self.titleNode)
let authorLayout = TextNode.asyncLayout(self.authorNode)
let makeMeasureLayout = TextNode.asyncLayout(self.measureNode)
let inputActivitiesLayout = self.inputActivitiesNode.asyncLayout()
let badgeLayout = self.badgeNode.asyncLayout()
let mentionBadgeLayout = self.mentionBadgeNode.asyncLayout()
@ -1105,21 +1110,21 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
}
badgeSize = max(badgeSize, reorderInset)
let (authorLayout, authorApply) = authorLayout(TextNodeLayoutArguments(attributedString: hideAuthor ? nil : authorAttributedString, backgroundColor: nil, minimumNumberOfLines: 1, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: rawContentWidth - badgeSize, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 1.0, bottom: 2.0, right: 1.0)))
let (authorLayout, authorApply) = authorLayout(TextNodeLayoutArguments(attributedString: hideAuthor ? nil : authorAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: rawContentWidth - badgeSize, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 1.0, bottom: 2.0, right: 1.0)))
var textCutout: TextNodeCutout?
if !textLeftCutout.isZero {
textCutout = TextNodeCutout(topLeft: CGSize(width: textLeftCutout, height: 4.0), topRight: nil, bottomRight: nil)
}
let (textLayout, textApply) = textLayout(TextNodeLayoutArguments(attributedString: textAttributedString, backgroundColor: nil, minimumNumberOfLines: authorAttributedString == nil ? 2 : 1, maximumNumberOfLines: authorAttributedString == nil ? 2 : 1, truncationType: .end, constrainedSize: CGSize(width: rawContentWidth - badgeSize, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: textCutout, insets: UIEdgeInsets(top: 2.0, left: 1.0, bottom: 2.0, right: 1.0)))
let (textLayout, textApply) = textLayout(TextNodeLayoutArguments(attributedString: textAttributedString, backgroundColor: nil, maximumNumberOfLines: authorAttributedString == nil ? 2 : 1, truncationType: .end, constrainedSize: CGSize(width: rawContentWidth - badgeSize, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: textCutout, insets: UIEdgeInsets(top: 2.0, left: 1.0, bottom: 2.0, right: 1.0)))
let titleRectWidth = rawContentWidth - dateLayout.size.width - 10.0 - statusWidth - titleIconsWidth
let (titleLayout, titleApply) = titleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, minimumNumberOfLines: 1, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: titleRectWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (titleLayout, titleApply) = titleLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: titleRectWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
var inputActivitiesSize: CGSize?
var inputActivitiesApply: (() -> Void)?
if let inputActivities = inputActivities, !inputActivities.isEmpty {
let (size, apply) = inputActivitiesLayout(CGSize(width: rawContentWidth - badgeSize, height: 40.0), item.presentationData.strings, item.presentationData.theme.chatList.messageTextColor, item.index.messageIndex.id.peerId, inputActivities)
let (size, apply) = inputActivitiesLayout(CGSize(width: rawContentWidth - badgeSize, height: 40.0), item.presentationData, item.presentationData.theme.chatList.messageTextColor, item.index.messageIndex.id.peerId, inputActivities)
inputActivitiesSize = size
inputActivitiesApply = apply
}
@ -1177,15 +1182,22 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
animateContent = true
}
let measureString = NSAttributedString(string: "A", font: titleFont, textColor: .black)
let (measureLayout, measureApply) = makeMeasureLayout(TextNodeLayoutArguments(attributedString: titleAttributedString, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: titleRectWidth, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let titleSpacing: CGFloat = -1.0
let authorSpacing: CGFloat = -3.0
var itemHeight: CGFloat = 8.0 * 2.0 + titleLayout.size.height + titleSpacing
if authorLayout.size.height.isZero {
var itemHeight: CGFloat = 8.0 * 2.0 + 1.0
itemHeight += measureLayout.size.height * 3.0
itemHeight += titleSpacing
itemHeight += authorSpacing
/*if authorLayout.size.height.isZero {
itemHeight += textLayout.size.height
} else {
itemHeight += authorLayout.size.height
itemHeight += authorSpacing + textLayout.size.height
}
}*/
let rawContentRect = CGRect(origin: CGPoint(x: 2.0, y: layoutOffset + 8.0), size: CGSize(width: rawContentWidth, height: itemHeight - 12.0 - 9.0))
@ -1209,6 +1221,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
return (layout, { [weak self] synchronousLoads, animated in
if let strongSelf = self {
strongSelf.layoutParams = (item, first, last, firstWithHeader, nextIsPinned, params, countersSize)
strongSelf.currentItemHeight = itemHeight
strongSelf.contentImageMedia = contentImageMedia
strongSelf.cachedChatListText = chatListText
strongSelf.cachedChatListSearchResult = chatListSearchResult
@ -1339,7 +1352,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
onlineIcon = PresentationResourcesChatList.recentStatusOnlineIcon(item.presentationData.theme, state: .regular)
}
strongSelf.onlineNode.setImage(onlineIcon)
let _ = measureApply()
let _ = dateApply()
let _ = textApply()
let _ = authorApply()
@ -1354,6 +1368,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
let statusSize = CGSize(width: 24.0, height: 24.0)
strongSelf.statusNode.frame = CGRect(origin: CGPoint(x: contentRect.origin.x + contentRect.size.width - dateLayout.size.width - statusSize.width, y: contentRect.origin.y + 2.0 - UIScreenPixel + floor((dateLayout.size.height - statusSize.height) / 2.0)), size: statusSize)
strongSelf.statusNode.fontSize = item.presentationData.fontSize.itemListBaseFontSize
let _ = strongSelf.statusNode.transitionToState(statusState, animated: animateContent)
if let _ = currentBadgeBackgroundImage {
@ -1817,7 +1832,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
if let item = self.item {
if case .groupReference = item.content {
self.layer.sublayerTransform = CATransform3DMakeTranslation(0.0, currentValue - self.bounds.size.height, 0.0)
self.layer.sublayerTransform = CATransform3DMakeTranslation(0.0, currentValue - (self.currentItemHeight ?? 0.0), 0.0)
} else {
var separatorFrame = self.separatorNode.frame
separatorFrame.origin.y = currentValue - UIScreenPixel

View file

@ -34,6 +34,8 @@ enum ChatListStatusNodeState: Equatable {
private let transitionDuration = 0.2
class ChatListStatusContentNode: ASDisplayNode {
var fontSize: CGFloat = 17.0
override init() {
super.init()
@ -57,6 +59,13 @@ class ChatListStatusContentNode: ASDisplayNode {
final class ChatListStatusNode: ASDisplayNode {
private(set) var state: ChatListStatusNodeState = .none
var fontSize: CGFloat = 17.0 {
didSet {
self.contentNode?.fontSize = self.fontSize
self.nextContentNode?.fontSize = self.fontSize
}
}
private var contentNode: ChatListStatusContentNode?
private var nextContentNode: ChatListStatusContentNode?
@ -66,6 +75,7 @@ final class ChatListStatusNode: ASDisplayNode {
self.state = state
let contentNode = state.contentNode()
contentNode?.fontSize = self.fontSize
if contentNode?.classForCoder != self.contentNode?.classForCoder {
contentNode?.updateWithState(state, animated: animated)
self.transitionToContentNode(contentNode, state: state, fromState: currentState, animated: animated, completion: completion)
@ -190,10 +200,12 @@ private func maybeAddRotationAnimation(_ layer: CALayer, duration: Double) {
private final class StatusChecksNodeParameters: NSObject {
let color: UIColor
let progress: CGFloat
let fontSize: CGFloat
init(color: UIColor, progress: CGFloat) {
init(color: UIColor, progress: CGFloat, fontSize: CGFloat) {
self.color = color
self.progress = progress
self.fontSize = fontSize
super.init()
}
@ -214,6 +226,12 @@ private class ChatListStatusChecksNode: ChatListStatusContentNode {
}
}
override var fontSize: CGFloat {
didSet {
self.setNeedsDisplay()
}
}
init(color: UIColor) {
self.color = color
@ -241,7 +259,7 @@ private class ChatListStatusChecksNode: ChatListStatusContentNode {
}
override func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? {
return StatusChecksNodeParameters(color: self.color, progress: self.effectiveProgress)
return StatusChecksNodeParameters(color: self.color, progress: self.effectiveProgress, fontSize: self.fontSize)
}
override func didEnterHierarchy() {
@ -261,6 +279,11 @@ private class ChatListStatusChecksNode: ChatListStatusContentNode {
return
}
let scaleFactor = min(1.4, parameters.fontSize / 17.0)
context.translateBy(x: bounds.width / 2.0, y: bounds.height / 2.0)
context.scaleBy(x: scaleFactor, y: scaleFactor)
context.translateBy(x: -bounds.width / 2.0, y: -bounds.height / 2.0)
let progress = parameters.progress
context.setStrokeColor(parameters.color.cgColor)

View file

@ -10,8 +10,6 @@ import TelegramPresentationData
import ChatTitleActivityNode
import LocalizedPeerData
private let textFont = Font.regular(15.0)
final class ChatListInputActivitiesNode: ASDisplayNode {
private let activityNode: ChatTitleActivityNode
@ -23,8 +21,12 @@ final class ChatListInputActivitiesNode: ASDisplayNode {
self.addSubnode(self.activityNode)
}
func asyncLayout() -> (CGSize, PresentationStrings, UIColor, PeerId, [(Peer, PeerInputActivity)]) -> (CGSize, () -> Void) {
return { [weak self] boundingSize, strings, color, peerId, activities in
func asyncLayout() -> (CGSize, ChatListPresentationData, UIColor, PeerId, [(Peer, PeerInputActivity)]) -> (CGSize, () -> Void) {
return { [weak self] boundingSize, presentationData, color, peerId, activities in
let strings = presentationData.strings
let textFont = Font.regular(floor(presentationData.fontSize.itemListBaseFontSize * 15.0 / 17.0))
var state = ChatTitleActivityNodeState.none
if !activities.isEmpty {

View file

@ -108,6 +108,7 @@ public class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDeleg
private let maskNode: ASImageNode
private let titleNode: TextNode
private let measureTitleSizeNode: TextNode
private let textNode: TextFieldNode
private let clearIconNode: ASImageNode
private let clearButtonNode: HighlightableButtonNode
@ -131,6 +132,7 @@ public class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDeleg
self.maskNode = ASImageNode()
self.titleNode = TextNode()
self.measureTitleSizeNode = TextNode()
self.textNode = TextFieldNode()
self.clearIconNode = ASImageNode()
@ -185,6 +187,7 @@ public class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDeleg
public func asyncLayout() -> (_ item: ItemListSingleLineInputItem, _ params: ListViewItemLayoutParams, _ neighbors: ItemListNeighbors) -> (ListViewItemNodeLayout, () -> Void) {
let makeTitleLayout = TextNode.asyncLayout(self.titleNode)
let makeMeasureTitleSizeLayout = TextNode.asyncLayout(self.measureTitleSizeNode)
let currentItem = self.item
@ -215,9 +218,11 @@ public class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDeleg
let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - 32.0 - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let (measureTitleLayout, measureTitleSizeApply) = makeMeasureTitleSizeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "A", font: Font.regular(item.presentationData.fontSize.itemListBaseFontSize)), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - 32.0 - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
let separatorHeight = UIScreenPixel
let contentSize = CGSize(width: params.width, height: titleLayout.size.height + 22.0)
let contentSize = CGSize(width: params.width, height: max(titleLayout.size.height, measureTitleLayout.size.height) + 22.0)
let insets = itemListNeighborsGroupedInsets(neighbors)
let layout = ListViewItemNodeLayout(contentSize: contentSize, insets: insets)
@ -246,6 +251,8 @@ public class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDeleg
let _ = titleApply()
strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: leftInset, y: floor((layout.contentSize.height - titleLayout.size.height) / 2.0)), size: titleLayout.size)
let _ = measureTitleSizeApply()
let secureEntry: Bool
let capitalizationType: UITextAutocapitalizationType
let autocorrectionType: UITextAutocorrectionType

View file

@ -77,7 +77,7 @@ final class ReactionNode: ASDisplayNode {
self.animationNode.automaticallyLoadFirstFrame = loadFirstFrame
self.animationNode.playToCompletionOnStop = true
var intrinsicSize = CGSize(width: maximizedReactionSize + 18.0, height: maximizedReactionSize + 18.0)
var intrinsicSize = CGSize(width: maximizedReactionSize + 14.0, height: maximizedReactionSize + 14.0)
self.imageNode = ASImageNode()
switch reaction {
@ -195,9 +195,8 @@ final class ReactionSelectionNode: ASDisplayNode {
private let hapticFeedback = HapticFeedback()
private var shadowBlur: CGFloat = 8.0
private var minimizedReactionSize: CGFloat = 30.0
private var maximizedReactionSize: CGFloat = 60.0
private var smallCircleSize: CGFloat = 8.0
private var minimizedReactionSize: CGFloat = 28.0
private var smallCircleSize: CGFloat = 14.0
private var isRightAligned: Bool = false
@ -246,44 +245,17 @@ final class ReactionSelectionNode: ASDisplayNode {
isRightAligned = true
}
if isInitial && self.reactionNodes.isEmpty {
let availableContentWidth = constrainedSize.width //max(100.0, initialAnchorX)
var minimizedReactionSize = (availableContentWidth - self.maximizedReactionSize) / (CGFloat(self.reactions.count - 1) + CGFloat(self.reactions.count + 1) * 0.2)
minimizedReactionSize = max(16.0, floor(minimizedReactionSize))
minimizedReactionSize = min(30.0, minimizedReactionSize)
self.minimizedReactionSize = minimizedReactionSize
self.shadowBlur = floor(minimizedReactionSize * 0.26)
self.smallCircleSize = 8.0
let backgroundHeight = floor(minimizedReactionSize * 1.4)
self.backgroundNode.image = generateBubbleImage(foreground: .white, diameter: backgroundHeight, shadowBlur: self.shadowBlur)
self.backgroundShadowNode.image = generateBubbleShadowImage(shadow: UIColor(white: 0.0, alpha: 0.2), diameter: backgroundHeight, shadowBlur: self.shadowBlur)
for i in 0 ..< self.bubbleNodes.count {
self.bubbleNodes[i].0.image = generateBubbleImage(foreground: .white, diameter: CGFloat(i + 1) * self.smallCircleSize, shadowBlur: self.shadowBlur)
self.bubbleNodes[i].1.image = generateBubbleShadowImage(shadow: UIColor(white: 0.0, alpha: 0.2), diameter: CGFloat(i + 1) * self.smallCircleSize, shadowBlur: self.shadowBlur)
}
self.reactionNodes = self.reactions.map { reaction -> ReactionNode in
return ReactionNode(account: self.account, theme: self.theme, reaction: reaction, maximizedReactionSize: self.maximizedReactionSize, loadFirstFrame: true)
}
self.reactionNodes.forEach(self.addSubnode(_:))
}
let backgroundHeight: CGFloat = floor(self.minimizedReactionSize * 1.4)
let reactionSpacing: CGFloat = floor(self.minimizedReactionSize * 0.2)
let minimizedReactionVerticalInset: CGFloat = floor((backgroundHeight - minimizedReactionSize) / 2.0)
let contentWidth: CGFloat = CGFloat(self.reactionNodes.count - 1) * (minimizedReactionSize) + maximizedReactionSize + CGFloat(self.reactionNodes.count + 1) * reactionSpacing
let reactionSideInset: CGFloat = 10.0
var reactionSpacing: CGFloat = 6.0
let minReactionSpacing: CGFloat = 2.0
let minimizedReactionSize = self.minimizedReactionSize
let contentWidth: CGFloat = CGFloat(self.reactions.count) * (minimizedReactionSize) + CGFloat(self.reactions.count - 1) * reactionSpacing + reactionSideInset * 2.0
let spaceForMaximizedReaction = CGFloat(self.reactions.count - 1) * reactionSpacing - CGFloat(self.reactions.count - 1) * minReactionSpacing
let maximizedReactionSize: CGFloat = minimizedReactionSize + spaceForMaximizedReaction
let backgroundHeight: CGFloat = floor(self.minimizedReactionSize * 1.8)
var backgroundFrame = CGRect(origin: CGPoint(x: -shadowBlur, y: -shadowBlur), size: CGSize(width: contentWidth + shadowBlur * 2.0, height: backgroundHeight + shadowBlur * 2.0))
if isRightAligned {
backgroundFrame = backgroundFrame.offsetBy(dx: initialAnchorX - contentWidth + backgroundHeight / 2.0, dy: startingPoint.y - backgroundHeight - 16.0)
} else {
backgroundFrame = backgroundFrame.offsetBy(dx: initialAnchorX - backgroundHeight / 2.0, dy: startingPoint.y - backgroundHeight - 16.0)
}
backgroundFrame = backgroundFrame.offsetBy(dx: floor((constrainedSize.width - contentWidth) / 2.0), dy: startingPoint.y - backgroundHeight - 12.0)
backgroundFrame.origin.x = max(0.0, backgroundFrame.minX)
backgroundFrame.origin.x = min(constrainedSize.width - backgroundFrame.width, backgroundFrame.minX)
@ -295,14 +267,44 @@ final class ReactionSelectionNode: ASDisplayNode {
if let reaction = self.reactions.last, case .reply = reaction {
maximizedIndex = self.reactions.count - 1
}
if backgroundFrame.insetBy(dx: -10.0, dy: -10.0).contains(touchPoint) {
if backgroundFrame.insetBy(dx: -10.0, dy: -10.0).offsetBy(dx: 0.0, dy: 10.0).contains(touchPoint) {
maximizedIndex = Int(((touchPoint.x - anchorMinX) / (anchorMaxX - anchorMinX)) * CGFloat(self.reactionNodes.count))
maximizedIndex = max(0, min(self.reactionNodes.count - 1, maximizedIndex))
}
if maximizedIndex == -1 {
let interReactionSpacing: CGFloat
if maximizedIndex != -1 {
interReactionSpacing = minReactionSpacing
} else {
interReactionSpacing = reactionSpacing
}
if isInitial && self.reactionNodes.isEmpty {
let availableContentWidth = constrainedSize.width //max(100.0, initialAnchorX)
self.shadowBlur = floor(minimizedReactionSize * 0.26)
self.smallCircleSize = 14.0
self.backgroundNode.image = generateBubbleImage(foreground: .white, diameter: backgroundHeight, shadowBlur: self.shadowBlur)
self.backgroundShadowNode.image = generateBubbleShadowImage(shadow: UIColor(white: 0.0, alpha: 0.2), diameter: backgroundHeight, shadowBlur: self.shadowBlur)
for i in 0 ..< self.bubbleNodes.count {
self.bubbleNodes[i].0.image = generateBubbleImage(foreground: .white, diameter: CGFloat(i + 1) * self.smallCircleSize, shadowBlur: self.shadowBlur)
self.bubbleNodes[i].1.image = generateBubbleShadowImage(shadow: UIColor(white: 0.0, alpha: 0.2), diameter: CGFloat(i + 1) * self.smallCircleSize, shadowBlur: self.shadowBlur)
}
self.reactionNodes = self.reactions.map { reaction -> ReactionNode in
return ReactionNode(account: self.account, theme: self.theme, reaction: reaction, maximizedReactionSize: maximizedReactionSize - 12.0, loadFirstFrame: true)
}
self.reactionNodes.forEach(self.addSubnode(_:))
}
let minimizedReactionVerticalInset: CGFloat = floor((backgroundHeight - minimizedReactionSize) / 2.0)
/*if maximizedIndex == -1 {
backgroundFrame.size.width -= maximizedReactionSize - minimizedReactionSize
backgroundFrame.origin.x += maximizedReactionSize - minimizedReactionSize
}
}*/
self.isRightAligned = isRightAligned
@ -315,8 +317,8 @@ final class ReactionSelectionNode: ASDisplayNode {
backgroundTransition.updateFrame(node: self.backgroundNode, frame: backgroundFrame)
backgroundTransition.updateFrame(node: self.backgroundShadowNode, frame: backgroundFrame)
var reactionX: CGFloat = backgroundFrame.minX + shadowBlur + reactionSpacing
if offsetFromStart > backgroundFrame.maxX - shadowBlur || offsetFromStart < backgroundFrame.minX {
var reactionX: CGFloat = backgroundFrame.minX + shadowBlur + reactionSideInset
if maximizedIndex != -1 {
self.hasSelectedNode = false
} else {
self.hasSelectedNode = true
@ -353,14 +355,14 @@ final class ReactionSelectionNode: ASDisplayNode {
var reactionFrame = CGRect(origin: CGPoint(x: reactionX, y: backgroundFrame.maxY - shadowBlur - minimizedReactionVerticalInset - reactionSize), size: CGSize(width: reactionSize, height: reactionSize))
if isMaximized {
reactionFrame.origin.x -= 9.0
reactionFrame.size.width += 18.0
reactionFrame.origin.x -= 7.0
reactionFrame.size.width += 14.0
}
self.reactionNodes[i].updateLayout(size: reactionFrame.size, scale: reactionFrame.size.width / (maximizedReactionSize + 18.0), transition: transition, displayText: isMaximized)
self.reactionNodes[i].updateLayout(size: reactionFrame.size, scale: reactionFrame.size.width / (maximizedReactionSize + 14.0), transition: transition, displayText: isMaximized)
transition.updateFrame(node: self.reactionNodes[i], frame: reactionFrame, beginWithCurrentState: true)
reactionX += reactionSize + reactionSpacing
reactionX += reactionSize + interReactionSpacing
}
let mainBubbleFrame = CGRect(origin: CGPoint(x: anchorX - self.smallCircleSize - shadowBlur, y: backgroundFrame.maxY - shadowBlur - self.smallCircleSize - shadowBlur), size: CGSize(width: self.smallCircleSize * 2.0 + shadowBlur * 2.0, height: self.smallCircleSize * 2.0 + shadowBlur * 2.0))

View file

@ -16,6 +16,7 @@ public final class ReactionSwipeGestureRecognizer: UIPanGestureRecognizer {
public var availableReactions: (() -> [ReactionGestureItem])?
public var getReactionContainer: (() -> ReactionSelectionParentNode?)?
public var getAnchorPoint: (() -> CGPoint?)?
public var shouldElevateAnchorPoint: (() -> Bool)?
public var began: (() -> Void)?
public var updateOffset: ((CGFloat, Bool) -> Void)?
public var completed: ((ReactionGestureItem?) -> Void)?
@ -99,7 +100,9 @@ public final class ReactionSwipeGestureRecognizer: UIPanGestureRecognizer {
self.f()
}
}
let activationTimer = Timer(timeInterval: 0.1, target: TimerTarget { [weak self] in
let elevate = self.shouldElevateAnchorPoint?() ?? false
let activationTimer = Timer(timeInterval: elevate ? 0.1 : 0.01, target: TimerTarget { [weak self] in
guard let strongSelf = self else {
return
}
@ -108,7 +111,9 @@ public final class ReactionSwipeGestureRecognizer: UIPanGestureRecognizer {
let location = strongSelf.currentLocation
if !strongSelf.currentReactions.isEmpty, let reactionContainer = strongSelf.getReactionContainer?(), let localAnchorPoint = strongSelf.getAnchorPoint?() {
strongSelf.currentContainer = reactionContainer
let reactionContainerLocation = reactionContainer.view.convert(localAnchorPoint, from: strongSelf.view)
//let reactionContainerLocation = reactionContainer.view.convert(localAnchorPoint, from: strongSelf.view)
let elevate = strongSelf.shouldElevateAnchorPoint?() ?? false
let reactionContainerLocation = reactionContainer.view.convert(location, from: nil).offsetBy(dx: 0.0, dy: elevate ? -44.0 : 22.0)
let reactionContainerTouchPoint = reactionContainer.view.convert(location, from: nil)
strongSelf.currentAnchorPoint = reactionContainerLocation
strongSelf.currentAnchorStartPoint = location

View file

@ -85,6 +85,7 @@ static_library(
"//submodules/PhoneNumberFormat:PhoneNumberFormat",
"//submodules/OpenInExternalAppUI:OpenInExternalAppUI",
"//submodules/AccountUtils:AccountUtils",
"//submodules/AuthTransferUI:AuthTransferUI",
],
frameworks = [
"$SDKROOT/System/Library/Frameworks/Foundation.framework",

View file

@ -427,7 +427,7 @@ private func privacyAndSecurityControllerEntries(presentationData: PresentationD
return entries
}
public func privacyAndSecurityController(context: AccountContext, initialSettings: AccountPrivacySettings? = nil, updatedSettings: ((AccountPrivacySettings?) -> Void)? = nil, focusOnItemTag: PrivacyAndSecurityEntryTag? = nil) -> ViewController {
public func privacyAndSecurityController(context: AccountContext, initialSettings: AccountPrivacySettings? = nil, updatedSettings: ((AccountPrivacySettings?) -> Void)? = nil, focusOnItemTag: PrivacyAndSecurityEntryTag? = nil, activeSessionsContext: ActiveSessionsContext? = nil) -> ViewController {
let statePromise = ValuePromise(PrivacyAndSecurityControllerState(), ignoreRepeated: true)
let stateValue = Atomic(value: PrivacyAndSecurityControllerState())
let updateState: ((PrivacyAndSecurityControllerState) -> PrivacyAndSecurityControllerState) -> Void = { f in
@ -450,7 +450,7 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
privacySettingsPromise.set(.single(initialSettings) |> then(requestAccountPrivacySettings(account: context.account) |> map(Optional.init)))
let blockedPeersContext = BlockedPeersContext(account: context.account)
let activeSessionsContext = ActiveSessionsContext(account: context.account)
let activeSessionsContext = activeSessionsContext ?? ActiveSessionsContext(account: context.account)
let updateTwoStepAuthDisposable = MetaDisposable()
actionsDisposable.add(updateTwoStepAuthDisposable)

View file

@ -10,6 +10,7 @@ import TelegramUIPreferences
import ItemListUI
import PresentationDataUtils
import AccountContext
import AuthTransferUI
private final class RecentSessionsControllerArguments {
let account: Account
@ -21,7 +22,9 @@ private final class RecentSessionsControllerArguments {
let removeWebSession: (Int64) -> Void
let terminateAllWebSessions: () -> Void
init(account: Account, setSessionIdWithRevealedOptions: @escaping (Int64?, Int64?) -> Void, removeSession: @escaping (Int64) -> Void, terminateOtherSessions: @escaping () -> Void, removeWebSession: @escaping (Int64) -> Void, terminateAllWebSessions: @escaping () -> Void) {
let addDevice: () -> Void
init(account: Account, setSessionIdWithRevealedOptions: @escaping (Int64?, Int64?) -> Void, removeSession: @escaping (Int64) -> Void, terminateOtherSessions: @escaping () -> Void, removeWebSession: @escaping (Int64) -> Void, terminateAllWebSessions: @escaping () -> Void, addDevice: @escaping () -> Void) {
self.account = account
self.setSessionIdWithRevealedOptions = setSessionIdWithRevealedOptions
self.removeSession = removeSession
@ -29,6 +32,8 @@ private final class RecentSessionsControllerArguments {
self.removeWebSession = removeWebSession
self.terminateAllWebSessions = terminateAllWebSessions
self.addDevice = addDevice
}
}
@ -84,6 +89,7 @@ private enum RecentSessionsEntry: ItemListNodeEntry {
case pendingSession(index: Int32, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, session: RecentAccountSession, enabled: Bool, editing: Bool, revealed: Bool)
case pendingSessionsInfo(PresentationTheme, String)
case otherSessionsHeader(PresentationTheme, String)
case addDevice(PresentationTheme, String)
case session(index: Int32, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, session: RecentAccountSession, enabled: Bool, editing: Bool, revealed: Bool)
case website(index: Int32, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, website: WebAuthorization, peer: Peer?, enabled: Bool, editing: Bool, revealed: Bool)
@ -93,7 +99,7 @@ private enum RecentSessionsEntry: ItemListNodeEntry {
return RecentSessionsSection.currentSession.rawValue
case .pendingSessionsHeader, .pendingSession, .pendingSessionsInfo:
return RecentSessionsSection.pendingSessions.rawValue
case .otherSessionsHeader, .session, .website:
case .otherSessionsHeader, .addDevice, .session, .website:
return RecentSessionsSection.otherSessions.rawValue
}
}
@ -118,6 +124,8 @@ private enum RecentSessionsEntry: ItemListNodeEntry {
return .index(6)
case .otherSessionsHeader:
return .index(7)
case .addDevice:
return .index(8)
case let .session(_, _, _, _, session, _, _, _):
return .session(session.hash)
case let .website(_, _, _, _, _, website, _, _, _, _):
@ -175,6 +183,12 @@ private enum RecentSessionsEntry: ItemListNodeEntry {
} else {
return false
}
case let .addDevice(lhsTheme, lhsText):
if case let .addDevice(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .currentSession(lhsTheme, lhsStrings, lhsDateTimeFormat, lhsSession):
if case let .currentSession(rhsTheme, rhsStrings, rhsDateTimeFormat, rhsSession) = rhs, lhsTheme === rhsTheme, lhsStrings === rhsStrings, lhsDateTimeFormat == rhsDateTimeFormat, lhsSession == rhsSession {
return true
@ -271,6 +285,10 @@ private enum RecentSessionsEntry: ItemListNodeEntry {
return ItemListTextItem(presentationData: presentationData, text: .plain(text), sectionId: self.section)
case let .otherSessionsHeader(theme, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .addDevice(theme, text):
return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
arguments.addDevice()
})
case let .session(_, theme, strings, dateTimeFormat, session, enabled, editing, revealed):
return ItemListRecentSessionItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, session: session, enabled: enabled, editable: true, editing: editing, revealed: revealed, sectionId: self.section, setSessionIdWithRevealedOptions: { previousId, id in
arguments.setSessionIdWithRevealedOptions(previousId, id)
@ -370,6 +388,8 @@ private func recentSessionsControllerEntries(presentationData: PresentationData,
entries.append(.otherSessionsHeader(presentationData.theme, presentationData.strings.AuthSessions_OtherSessions))
entries.append(.addDevice(presentationData.theme, presentationData.strings.AuthSessions_AddDevice))
let filteredSessions: [RecentAccountSession] = sessionsState.sessions.sorted(by: { lhs, rhs in
return lhs.activityDate > rhs.activityDate
})
@ -421,7 +441,10 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont
statePromise.set(stateValue.modify { f($0) })
}
activeSessionsContext.loadMore()
var presentControllerImpl: ((ViewController, ViewControllerPresentationArguments?) -> Void)?
var pushControllerImpl: ((ViewController) -> Void)?
let actionsDisposable = DisposableSet()
@ -575,6 +598,8 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont
ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })])
])
presentControllerImpl?(controller, ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}, addDevice: {
pushControllerImpl?(AuthTransferScanScreen(context: context, activeSessionsContext: activeSessionsContext))
})
let websitesSignal: Signal<([WebAuthorization], [PeerId : Peer])?, NoError> = .single(nil) |> then(webSessions(network: context.account.network) |> map(Optional.init))
@ -619,7 +644,7 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont
if let websites = websites, !websites.isEmpty {
title = .sectionControl([presentationData.strings.AuthSessions_Sessions, presentationData.strings.AuthSessions_LoggedIn], mode.rawValue)
} else {
title = .text(presentationData.strings.AuthSessions_Title)
title = .text(presentationData.strings.AuthSessions_DevicesTitle)
}
var animateChanges = true
@ -655,6 +680,9 @@ public func recentSessionsController(context: AccountContext, activeSessionsCont
controller.present(c, in: .window(.root), with: p)
}
}
pushControllerImpl = { [weak controller] c in
controller?.push(c)
}
return controller
}

View file

@ -36,6 +36,7 @@ import ContextUI
import WalletUI
import PhoneNumberFormat
import AccountUtils
import AuthTransferUI
private let avatarFont = avatarPlaceholderFont(size: 13.0)
@ -111,6 +112,7 @@ private final class SettingsItemArguments {
let keepPhone: () -> Void
let openPhoneNumberChange: () -> Void
let accountContextAction: (AccountRecordId, ASDisplayNode, ContextGesture?) -> Void
let openDevices: () -> Void
init(
accountManager: AccountManager,
@ -143,7 +145,8 @@ private final class SettingsItemArguments {
removeAccount: @escaping (AccountRecordId) -> Void,
keepPhone: @escaping () -> Void,
openPhoneNumberChange: @escaping () -> Void,
accountContextAction: @escaping (AccountRecordId, ASDisplayNode, ContextGesture?) -> Void
accountContextAction: @escaping (AccountRecordId, ASDisplayNode, ContextGesture?) -> Void,
openDevices: @escaping () -> Void
) {
self.accountManager = accountManager
self.avatarAndNameInfoContext = avatarAndNameInfoContext
@ -176,6 +179,7 @@ private final class SettingsItemArguments {
self.keepPhone = keepPhone
self.openPhoneNumberChange = openPhoneNumberChange
self.accountContextAction = accountContextAction
self.openDevices = openDevices
}
}
@ -204,6 +208,8 @@ private indirect enum SettingsEntry: ItemListNodeEntry {
case proxy(PresentationTheme, UIImage?, String, String)
case devices(PresentationTheme, UIImage?, String, String)
case savedMessages(PresentationTheme, UIImage?, String)
case recentCalls(PresentationTheme, UIImage?, String)
case stickers(PresentationTheme, UIImage?, String, String, [ArchivedStickerPackItem]?)
@ -222,22 +228,24 @@ private indirect enum SettingsEntry: ItemListNodeEntry {
var section: ItemListSectionId {
switch self {
case .userInfo, .setProfilePhoto, .setUsername:
return SettingsSection.info.rawValue
case .phoneInfo, .keepPhone, .changePhone:
return SettingsSection.phone.rawValue
case .account, .addAccount:
return SettingsSection.accounts.rawValue
case .proxy:
return SettingsSection.proxy.rawValue
case .savedMessages, .recentCalls, .stickers:
return SettingsSection.media.rawValue
case .notificationsAndSounds, .privacyAndSecurity, .dataAndStorage, .themes, .language:
return SettingsSection.generalSettings.rawValue
case .passport, .wallet, .watch :
return SettingsSection.advanced.rawValue
case .askAQuestion, .faq:
return SettingsSection.help.rawValue
case .userInfo, .setProfilePhoto, .setUsername:
return SettingsSection.info.rawValue
case .phoneInfo, .keepPhone, .changePhone:
return SettingsSection.phone.rawValue
case .account, .addAccount:
return SettingsSection.accounts.rawValue
case .proxy:
return SettingsSection.proxy.rawValue
case .devices:
return SettingsSection.media.rawValue
case .savedMessages, .recentCalls, .stickers:
return SettingsSection.media.rawValue
case .notificationsAndSounds, .privacyAndSecurity, .dataAndStorage, .themes, .language:
return SettingsSection.generalSettings.rawValue
case .passport, .wallet, .watch :
return SettingsSection.advanced.rawValue
case .askAQuestion, .faq:
return SettingsSection.help.rawValue
}
}
@ -267,26 +275,28 @@ private indirect enum SettingsEntry: ItemListNodeEntry {
return 1005
case .stickers:
return 1006
case .notificationsAndSounds:
case .devices:
return 1007
case .privacyAndSecurity:
case .notificationsAndSounds:
return 1008
case .dataAndStorage:
case .privacyAndSecurity:
return 1009
case .themes:
case .dataAndStorage:
return 1010
case .language:
case .themes:
return 1011
case .wallet:
case .language:
return 1012
case .passport:
case .wallet:
return 1013
case .watch:
case .passport:
return 1014
case .askAQuestion:
case .watch:
return 1015
case .faq:
case .askAQuestion:
return 1016
case .faq:
return 1017
}
}
@ -378,6 +388,12 @@ private indirect enum SettingsEntry: ItemListNodeEntry {
} else {
return false
}
case let .devices(lhsTheme, lhsImage, lhsText, lhsValue):
if case let .devices(rhsTheme, rhsImage, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsImage === rhsImage, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .savedMessages(lhsTheme, lhsImage, lhsText):
if case let .savedMessages(rhsTheme, rhsImage, rhsText) = rhs, lhsTheme === rhsTheme, lhsImage === rhsImage, lhsText == rhsText {
return true
@ -527,6 +543,10 @@ private indirect enum SettingsEntry: ItemListNodeEntry {
return ItemListDisclosureItem(presentationData: presentationData, icon: image, title: text, label: value, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
arguments.openProxy()
})
case let .devices(theme, image, text, value):
return ItemListDisclosureItem(presentationData: presentationData, icon: image, title: text, label: value, sectionId: ItemListSectionId(self.section), style: .blocks, action: {
arguments.openDevices()
})
case let .savedMessages(theme, image, text):
return ItemListDisclosureItem(presentationData: presentationData, icon: image, title: text, label: "", sectionId: ItemListSectionId(self.section), style: .blocks, action: {
arguments.openSavedMessages()
@ -589,7 +609,7 @@ private struct SettingsState: Equatable {
var isSearching: Bool
}
private func settingsEntries(account: Account, presentationData: PresentationData, state: SettingsState, view: PeerView, proxySettings: ProxySettings, notifyExceptions: NotificationExceptionsList?, notificationsAuthorizationStatus: AccessType, notificationsWarningSuppressed: Bool, unreadTrendingStickerPacks: Int, archivedPacks: [ArchivedStickerPackItem]?, privacySettings: AccountPrivacySettings?, hasWallet: Bool, hasPassport: Bool, hasWatchApp: Bool, accountsAndPeers: [(Account, Peer, Int32)], inAppNotificationSettings: InAppNotificationSettings, experimentalUISettings: ExperimentalUISettings, displayPhoneNumberConfirmation: Bool) -> [SettingsEntry] {
private func settingsEntries(account: Account, presentationData: PresentationData, state: SettingsState, view: PeerView, proxySettings: ProxySettings, notifyExceptions: NotificationExceptionsList?, notificationsAuthorizationStatus: AccessType, notificationsWarningSuppressed: Bool, unreadTrendingStickerPacks: Int, archivedPacks: [ArchivedStickerPackItem]?, privacySettings: AccountPrivacySettings?, hasWallet: Bool, hasPassport: Bool, hasWatchApp: Bool, accountsAndPeers: [(Account, Peer, Int32)], inAppNotificationSettings: InAppNotificationSettings, experimentalUISettings: ExperimentalUISettings, displayPhoneNumberConfirmation: Bool, otherSessionCount: Int) -> [SettingsEntry] {
var entries: [SettingsEntry] = []
if let peer = peerViewMainPeer(view) as? TelegramUser {
@ -638,6 +658,7 @@ private func settingsEntries(account: Account, presentationData: PresentationDat
entries.append(.savedMessages(presentationData.theme, PresentationResourcesSettings.savedMessages, presentationData.strings.Settings_SavedMessages))
entries.append(.recentCalls(presentationData.theme, PresentationResourcesSettings.recentCalls, presentationData.strings.CallSettings_RecentCalls))
entries.append(.stickers(presentationData.theme, PresentationResourcesSettings.stickers, presentationData.strings.ChatSettings_Stickers, unreadTrendingStickerPacks == 0 ? "" : "\(unreadTrendingStickerPacks)", archivedPacks))
entries.append(.devices(presentationData.theme, UIImage(bundleImageName: "Settings/MenuIcons/Sessions")?.precomposed(), presentationData.strings.Settings_Devices, otherSessionCount == 0 ? presentationData.strings.Settings_AddDevice : "\(otherSessionCount)"))
let notificationsWarning = shouldDisplayNotificationsPermissionWarning(status: notificationsAuthorizationStatus, suppressed: notificationsWarningSuppressed)
entries.append(.notificationsAndSounds(presentationData.theme, PresentationResourcesSettings.notifications, presentationData.strings.Settings_NotificationsAndSounds, notifyExceptions, notificationsWarning))
@ -817,6 +838,23 @@ public func settingsController(context: AccountContext, accountManager: AccountM
let displayPhoneNumberConfirmation = ValuePromise<Bool>(false)
let activeSessionsContextAndCountSignal = contextValue.get()
|> deliverOnMainQueue
|> mapToSignal { context -> Signal<(ActiveSessionsContext, Int), NoError> in
let activeSessionsContext = ActiveSessionsContext(account: context.account)
let otherSessionCount = activeSessionsContext.state
|> map { state -> Int in
return state.sessions.filter({ !$0.isCurrent }).count
}
|> distinctUntilChanged
return otherSessionCount
|> map { value in
return (activeSessionsContext, value)
}
}
let activeSessionsContextAndCount = Promise<(ActiveSessionsContext, Int)>()
activeSessionsContextAndCount.set(activeSessionsContextAndCountSignal)
let arguments = SettingsItemArguments(accountManager: accountManager, avatarAndNameInfoContext: avatarAndNameInfoContext, avatarTapAction: {
var updating = false
updateState {
@ -874,9 +912,13 @@ public func settingsController(context: AccountContext, accountManager: AccountM
let _ = (contextValue.get()
|> deliverOnMainQueue
|> take(1)).start(next: { context in
pushControllerImpl?(privacyAndSecurityController(context: context, initialSettings: privacySettingsValue, updatedSettings: { settings in
privacySettings.set(.single(settings))
}))
let _ = (activeSessionsContextAndCount.get()
|> deliverOnMainQueue
|> take(1)).start(next: { activeSessionsContext, _ in
pushControllerImpl?(privacyAndSecurityController(context: context, initialSettings: privacySettingsValue, updatedSettings: { settings in
privacySettings.set(.single(settings))
}, activeSessionsContext: activeSessionsContext))
})
})
}, openDataAndStorage: {
let _ = (contextValue.get()
@ -1044,6 +1086,16 @@ public func settingsController(context: AccountContext, accountManager: AccountM
} else {
gesture?.cancel()
}
}, openDevices: {
let _ = (activeSessionsContextAndCount.get()
|> deliverOnMainQueue
|> take(1)).start(next: { activeSessionsContext, count in
if count == 0 {
pushControllerImpl?(AuthTransferScanScreen(context: context, activeSessionsContext: activeSessionsContext))
} else {
pushControllerImpl?(recentSessionsController(context: context, activeSessionsContext: activeSessionsContext))
}
})
})
changeProfilePhotoImpl = {
@ -1280,8 +1332,10 @@ public func settingsController(context: AccountContext, accountManager: AccountM
return context.account.viewTracker.featuredStickerPacks()
}
let signal = combineLatest(queue: Queue.mainQueue(), contextValue.get(), updatedPresentationData, statePromise.get(), peerView, combineLatest(queue: Queue.mainQueue(), preferences, notifyExceptions.get(), notificationsAuthorizationStatus.get(), notificationsWarningSuppressed.get(), privacySettings.get(), displayPhoneNumberConfirmation.get()), combineLatest(featuredStickerPacks, archivedPacks.get()), combineLatest(hasWallet, hasPassport.get(), hasWatchApp), accountsAndPeers.get())
|> map { context, presentationData, state, view, preferencesAndExceptions, featuredAndArchived, hasWalletPassportAndWatch, accountsAndPeers -> (ItemListControllerState, (ItemListNodeState, Any)) in
let signal = combineLatest(queue: Queue.mainQueue(), contextValue.get(), updatedPresentationData, statePromise.get(), peerView, combineLatest(queue: Queue.mainQueue(), preferences, notifyExceptions.get(), notificationsAuthorizationStatus.get(), notificationsWarningSuppressed.get(), privacySettings.get(), displayPhoneNumberConfirmation.get()), combineLatest(featuredStickerPacks, archivedPacks.get()), combineLatest(hasWallet, hasPassport.get(), hasWatchApp), accountsAndPeers.get(), activeSessionsContextAndCount.get())
|> map { context, presentationData, state, view, preferencesAndExceptions, featuredAndArchived, hasWalletPassportAndWatch, accountsAndPeers, activeSessionsContextAndCount -> (ItemListControllerState, (ItemListNodeState, Any)) in
let otherSessionCount = activeSessionsContextAndCount.1
let proxySettings: ProxySettings = preferencesAndExceptions.0.entries[SharedDataKeys.proxySettings] as? ProxySettings ?? ProxySettings.defaultSettings
let inAppNotificationSettings: InAppNotificationSettings = preferencesAndExceptions.0.entries[ApplicationSpecificSharedDataKeys.inAppNotificationSettings] as? InAppNotificationSettings ?? InAppNotificationSettings.defaultSettings
let experimentalUISettings: ExperimentalUISettings = preferencesAndExceptions.0.entries[ApplicationSpecificSharedDataKeys.experimentalUISettings] as? ExperimentalUISettings ?? ExperimentalUISettings.defaultSettings
@ -1319,7 +1373,7 @@ public func settingsController(context: AccountContext, accountManager: AccountM
}, getNavigationController: getNavigationControllerImpl, exceptionsList: notifyExceptions.get(), archivedStickerPacks: archivedPacks.get(), privacySettings: privacySettings.get(), hasWallet: hasWallet)
let (hasWallet, hasPassport, hasWatchApp) = hasWalletPassportAndWatch
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: settingsEntries(account: context.account, presentationData: presentationData, state: state, view: view, proxySettings: proxySettings, notifyExceptions: preferencesAndExceptions.1, notificationsAuthorizationStatus: preferencesAndExceptions.2, notificationsWarningSuppressed: preferencesAndExceptions.3, unreadTrendingStickerPacks: unreadTrendingStickerPacks, archivedPacks: featuredAndArchived.1, privacySettings: preferencesAndExceptions.4, hasWallet: hasWallet, hasPassport: hasPassport, hasWatchApp: hasWatchApp, accountsAndPeers: accountsAndPeers.1, inAppNotificationSettings: inAppNotificationSettings, experimentalUISettings: experimentalUISettings, displayPhoneNumberConfirmation: preferencesAndExceptions.5), style: .blocks, searchItem: searchItem, initialScrollToItem: ListViewScrollToItem(index: 0, position: .top(-navigationBarSearchContentHeight), animated: false, curve: .Default(duration: 0.0), directionHint: .Up))
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: settingsEntries(account: context.account, presentationData: presentationData, state: state, view: view, proxySettings: proxySettings, notifyExceptions: preferencesAndExceptions.1, notificationsAuthorizationStatus: preferencesAndExceptions.2, notificationsWarningSuppressed: preferencesAndExceptions.3, unreadTrendingStickerPacks: unreadTrendingStickerPacks, archivedPacks: featuredAndArchived.1, privacySettings: preferencesAndExceptions.4, hasWallet: hasWallet, hasPassport: hasPassport, hasWatchApp: hasWatchApp, accountsAndPeers: accountsAndPeers.1, inAppNotificationSettings: inAppNotificationSettings, experimentalUISettings: experimentalUISettings, displayPhoneNumberConfirmation: preferencesAndExceptions.5, otherSessionCount: otherSessionCount), style: .blocks, searchItem: searchItem, initialScrollToItem: ListViewScrollToItem(index: 0, position: .top(-navigationBarSearchContentHeight), animated: false, curve: .Default(duration: 0.0), directionHint: .Up))
return (controllerState, (listState, arguments))
}

View file

@ -113,7 +113,6 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView
if case let .file(file) = presentationData.theme.chat.defaultWallpaper, file.id == 0 {
self.remoteChatBackgroundNode.isHidden = false
self.toolbarNode.setDoneEnabled(false)
} else {
self.remoteChatBackgroundNode.isHidden = true
}
@ -212,7 +211,8 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView
guard let strongSelf = self else {
return
}
if case let .file(file) = wallpaper {
switch wallpaper {
case let .file(file):
let dimensions = file.file.dimensions ?? PixelDimensions(width: 100, height: 100)
let displaySize = dimensions.cgSize.dividedByScreenScale().integralFloor
@ -267,6 +267,8 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView
}
strongSelf.remoteChatBackgroundNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(), imageSize: displaySize, boundingSize: displaySize, intrinsicInsets: UIEdgeInsets(), emptyColor: patternColor))()
default:
break
}
}
applyWallpaper(self.presentationData.chatWallpaper)
@ -519,6 +521,7 @@ private final class TextSizeSelectionControllerNode: ASDisplayNode, UIScrollView
self.toolbarNode.updatePresentationThemeSettings(presentationThemeSettings: self.presentationThemeSettings)
if let (layout, navigationBarHeight) = self.validLayout {
self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate)
self.recursivelyEnsureDisplaySynchronously(true)
}
}

View file

@ -53,6 +53,9 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-614138572] = { return Api.account.TmpPassword.parse_tmpPassword($0) }
dict[-2103600678] = { return Api.SecureRequiredType.parse_secureRequiredType($0) }
dict[41187252] = { return Api.SecureRequiredType.parse_secureRequiredTypeOneOf($0) }
dict[1654593920] = { return Api.auth.LoginToken.parse_loginToken($0) }
dict[110008598] = { return Api.auth.LoginToken.parse_loginTokenMigrateTo($0) }
dict[957176926] = { return Api.auth.LoginToken.parse_loginTokenSuccess($0) }
dict[1064139624] = { return Api.JSONValue.parse_jsonNull($0) }
dict[-952869270] = { return Api.JSONValue.parse_jsonBool($0) }
dict[736157604] = { return Api.JSONValue.parse_jsonNumber($0) }
@ -242,6 +245,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1870238482] = { return Api.Update.parse_updateDeleteScheduledMessages($0) }
dict[-2112423005] = { return Api.Update.parse_updateTheme($0) }
dict[357013699] = { return Api.Update.parse_updateMessageReactions($0) }
dict[-2027964103] = { return Api.Update.parse_updateGeoLiveViewed($0) }
dict[1448076945] = { return Api.Update.parse_updateLoginToken($0) }
dict[1558266229] = { return Api.PopularContact.parse_popularContact($0) }
dict[-373643672] = { return Api.FolderPeer.parse_folderPeer($0) }
dict[367766557] = { return Api.ChannelParticipant.parse_channelParticipant($0) }
@ -594,6 +599,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[70813275] = { return Api.InputStickeredMedia.parse_inputStickeredMediaDocument($0) }
dict[82699215] = { return Api.messages.FeaturedStickers.parse_featuredStickersNotModified($0) }
dict[-123893531] = { return Api.messages.FeaturedStickers.parse_featuredStickers($0) }
dict[1375940666] = { return Api.auth.LoginTokenInfo.parse_loginTokenInfo($0) }
dict[-2048646399] = { return Api.PhoneCallDiscardReason.parse_phoneCallDiscardReasonMissed($0) }
dict[-527056480] = { return Api.PhoneCallDiscardReason.parse_phoneCallDiscardReasonDisconnect($0) }
dict[1471006352] = { return Api.PhoneCallDiscardReason.parse_phoneCallDiscardReasonHangup($0) }
@ -868,6 +874,8 @@ public struct Api {
_1.serialize(buffer, boxed)
case let _1 as Api.SecureRequiredType:
_1.serialize(buffer, boxed)
case let _1 as Api.auth.LoginToken:
_1.serialize(buffer, boxed)
case let _1 as Api.JSONValue:
_1.serialize(buffer, boxed)
case let _1 as Api.Photo:
@ -1244,6 +1252,8 @@ public struct Api {
_1.serialize(buffer, boxed)
case let _1 as Api.messages.FeaturedStickers:
_1.serialize(buffer, boxed)
case let _1 as Api.auth.LoginTokenInfo:
_1.serialize(buffer, boxed)
case let _1 as Api.PhoneCallDiscardReason:
_1.serialize(buffer, boxed)
case let _1 as Api.NearestDc:

View file

@ -4145,6 +4145,8 @@ public extension Api {
case updateDeleteScheduledMessages(peer: Api.Peer, messages: [Int32])
case updateTheme(theme: Api.Theme)
case updateMessageReactions(peer: Api.Peer, msgId: Int32, reactions: Api.MessageReactions)
case updateGeoLiveViewed(peer: Api.Peer, msgId: Int32)
case updateLoginToken
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
@ -4776,6 +4778,19 @@ public extension Api {
peer.serialize(buffer, true)
serializeInt32(msgId, buffer: buffer, boxed: false)
reactions.serialize(buffer, true)
break
case .updateGeoLiveViewed(let peer, let msgId):
if boxed {
buffer.appendInt32(-2027964103)
}
peer.serialize(buffer, true)
serializeInt32(msgId, buffer: buffer, boxed: false)
break
case .updateLoginToken:
if boxed {
buffer.appendInt32(1448076945)
}
break
}
}
@ -4932,6 +4947,10 @@ public extension Api {
return ("updateTheme", [("theme", theme)])
case .updateMessageReactions(let peer, let msgId, let reactions):
return ("updateMessageReactions", [("peer", peer), ("msgId", msgId), ("reactions", reactions)])
case .updateGeoLiveViewed(let peer, let msgId):
return ("updateGeoLiveViewed", [("peer", peer), ("msgId", msgId)])
case .updateLoginToken:
return ("updateLoginToken", [])
}
}
@ -6211,6 +6230,25 @@ public extension Api {
return nil
}
}
public static func parse_updateGeoLiveViewed(_ reader: BufferReader) -> Update? {
var _1: Api.Peer?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.Peer
}
var _2: Int32?
_2 = reader.readInt32()
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.Update.updateGeoLiveViewed(peer: _1!, msgId: _2!)
}
else {
return nil
}
}
public static func parse_updateLoginToken(_ reader: BufferReader) -> Update? {
return Api.Update.updateLoginToken
}
}
public enum PopularContact: TypeConstructorDescription {

View file

@ -494,6 +494,90 @@ public struct payments {
}
public extension Api {
public struct auth {
public enum LoginToken: TypeConstructorDescription {
case loginToken(expires: Int32, token: Buffer)
case loginTokenMigrateTo(dcId: Int32, token: Buffer)
case loginTokenSuccess(authorization: Api.auth.Authorization)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .loginToken(let expires, let token):
if boxed {
buffer.appendInt32(1654593920)
}
serializeInt32(expires, buffer: buffer, boxed: false)
serializeBytes(token, buffer: buffer, boxed: false)
break
case .loginTokenMigrateTo(let dcId, let token):
if boxed {
buffer.appendInt32(110008598)
}
serializeInt32(dcId, buffer: buffer, boxed: false)
serializeBytes(token, buffer: buffer, boxed: false)
break
case .loginTokenSuccess(let authorization):
if boxed {
buffer.appendInt32(957176926)
}
authorization.serialize(buffer, true)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .loginToken(let expires, let token):
return ("loginToken", [("expires", expires), ("token", token)])
case .loginTokenMigrateTo(let dcId, let token):
return ("loginTokenMigrateTo", [("dcId", dcId), ("token", token)])
case .loginTokenSuccess(let authorization):
return ("loginTokenSuccess", [("authorization", authorization)])
}
}
public static func parse_loginToken(_ reader: BufferReader) -> LoginToken? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Buffer?
_2 = parseBytes(reader)
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.auth.LoginToken.loginToken(expires: _1!, token: _2!)
}
else {
return nil
}
}
public static func parse_loginTokenMigrateTo(_ reader: BufferReader) -> LoginToken? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Buffer?
_2 = parseBytes(reader)
let _c1 = _1 != nil
let _c2 = _2 != nil
if _c1 && _c2 {
return Api.auth.LoginToken.loginTokenMigrateTo(dcId: _1!, token: _2!)
}
else {
return nil
}
}
public static func parse_loginTokenSuccess(_ reader: BufferReader) -> LoginToken? {
var _1: Api.auth.Authorization?
if let signature = reader.readInt32() {
_1 = Api.parse(reader, signature: signature) as? Api.auth.Authorization
}
let _c1 = _1 != nil
if _c1 {
return Api.auth.LoginToken.loginTokenSuccess(authorization: _1!)
}
else {
return nil
}
}
}
public enum Authorization: TypeConstructorDescription {
case authorization(flags: Int32, tmpSessions: Int32?, user: Api.User)
case authorizationSignUpRequired(flags: Int32, termsOfService: Api.help.TermsOfService?)
@ -775,6 +859,76 @@ public struct auth {
return Api.auth.CodeType.codeTypeFlashCall
}
}
public enum LoginTokenInfo: TypeConstructorDescription {
case loginTokenInfo(dcId: Int32, authKeyId: Int64, deviceModel: String, platform: String, systemVersion: String, apiId: Int32, appName: String, appVersion: String, ip: String, region: String)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self {
case .loginTokenInfo(let dcId, let authKeyId, let deviceModel, let platform, let systemVersion, let apiId, let appName, let appVersion, let ip, let region):
if boxed {
buffer.appendInt32(1375940666)
}
serializeInt32(dcId, buffer: buffer, boxed: false)
serializeInt64(authKeyId, buffer: buffer, boxed: false)
serializeString(deviceModel, buffer: buffer, boxed: false)
serializeString(platform, buffer: buffer, boxed: false)
serializeString(systemVersion, buffer: buffer, boxed: false)
serializeInt32(apiId, buffer: buffer, boxed: false)
serializeString(appName, buffer: buffer, boxed: false)
serializeString(appVersion, buffer: buffer, boxed: false)
serializeString(ip, buffer: buffer, boxed: false)
serializeString(region, buffer: buffer, boxed: false)
break
}
}
public func descriptionFields() -> (String, [(String, Any)]) {
switch self {
case .loginTokenInfo(let dcId, let authKeyId, let deviceModel, let platform, let systemVersion, let apiId, let appName, let appVersion, let ip, let region):
return ("loginTokenInfo", [("dcId", dcId), ("authKeyId", authKeyId), ("deviceModel", deviceModel), ("platform", platform), ("systemVersion", systemVersion), ("apiId", apiId), ("appName", appName), ("appVersion", appVersion), ("ip", ip), ("region", region)])
}
}
public static func parse_loginTokenInfo(_ reader: BufferReader) -> LoginTokenInfo? {
var _1: Int32?
_1 = reader.readInt32()
var _2: Int64?
_2 = reader.readInt64()
var _3: String?
_3 = parseString(reader)
var _4: String?
_4 = parseString(reader)
var _5: String?
_5 = parseString(reader)
var _6: Int32?
_6 = reader.readInt32()
var _7: String?
_7 = parseString(reader)
var _8: String?
_8 = parseString(reader)
var _9: String?
_9 = parseString(reader)
var _10: String?
_10 = parseString(reader)
let _c1 = _1 != nil
let _c2 = _2 != nil
let _c3 = _3 != nil
let _c4 = _4 != nil
let _c5 = _5 != nil
let _c6 = _6 != nil
let _c7 = _7 != nil
let _c8 = _8 != nil
let _c9 = _9 != nil
let _c10 = _10 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 {
return Api.auth.LoginTokenInfo.loginTokenInfo(dcId: _1!, authKeyId: _2!, deviceModel: _3!, platform: _4!, systemVersion: _5!, apiId: _6!, appName: _7!, appVersion: _8!, ip: _9!, region: _10!)
}
else {
return nil
}
}
}
public enum SentCodeType: TypeConstructorDescription {
case sentCodeTypeApp(length: Int32)

View file

@ -4139,6 +4139,63 @@ public extension Api {
return result
})
}
public static func exportLoginToken(apiId: Int32, apiHash: String) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.auth.LoginToken>) {
let buffer = Buffer()
buffer.appendInt32(434003159)
serializeInt32(apiId, buffer: buffer, boxed: false)
serializeString(apiHash, buffer: buffer, boxed: false)
return (FunctionDescription(name: "auth.exportLoginToken", parameters: [("apiId", apiId), ("apiHash", apiHash)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.LoginToken? in
let reader = BufferReader(buffer)
var result: Api.auth.LoginToken?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.auth.LoginToken
}
return result
})
}
public static func importLoginToken(token: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.auth.LoginToken>) {
let buffer = Buffer()
buffer.appendInt32(-1783866140)
serializeBytes(token, buffer: buffer, boxed: false)
return (FunctionDescription(name: "auth.importLoginToken", parameters: [("token", token)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.LoginToken? in
let reader = BufferReader(buffer)
var result: Api.auth.LoginToken?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.auth.LoginToken
}
return result
})
}
public static func acceptLoginToken(token: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Updates>) {
let buffer = Buffer()
buffer.appendInt32(1122447801)
serializeBytes(token, buffer: buffer, boxed: false)
return (FunctionDescription(name: "auth.acceptLoginToken", parameters: [("token", token)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in
let reader = BufferReader(buffer)
var result: Api.Updates?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.Updates
}
return result
})
}
public static func checkLoginToken(token: Buffer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.auth.LoginTokenInfo>) {
let buffer = Buffer()
buffer.appendInt32(2102383792)
serializeBytes(token, buffer: buffer, boxed: false)
return (FunctionDescription(name: "auth.checkLoginToken", parameters: [("token", token)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.auth.LoginTokenInfo? in
let reader = BufferReader(buffer)
var result: Api.auth.LoginTokenInfo?
if let signature = reader.readInt32() {
result = Api.parse(reader, signature: signature) as? Api.auth.LoginTokenInfo
}
return result
})
}
}
public struct bots {
public static func sendCustomRequest(customMethod: String, params: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.DataJSON>) {

View file

@ -61,6 +61,12 @@ public class UnauthorizedAccount {
public let testingEnvironment: Bool
public let postbox: Postbox
public let network: Network
private let stateManager: UnauthorizedAccountStateManager
private let updateLoginTokenPipe = ValuePipe<Void>()
public var updateLoginTokenEvents: Signal<Void, NoError> {
return self.updateLoginTokenPipe.signal()
}
public var masterDatacenterId: Int32 {
return Int32(self.network.mtProto.datacenterId)
@ -76,6 +82,10 @@ public class UnauthorizedAccount {
self.testingEnvironment = testingEnvironment
self.postbox = postbox
self.network = network
let updateLoginTokenPipe = self.updateLoginTokenPipe
self.stateManager = UnauthorizedAccountStateManager(network: network, updateLoginToken: {
updateLoginTokenPipe.putNext(Void())
})
network.shouldKeepConnection.set(self.shouldBeServiceTaskMaster.get()
|> map { mode -> Bool in
@ -99,6 +109,8 @@ public class UnauthorizedAccount {
}
network.context.beginExplicitBackupAddressDiscovery()
})
self.stateManager.reset()
}
public func changedMasterDatacenterId(accountManager: AccountManager, masterDatacenterId: Int32) -> Signal<UnauthorizedAccount, NoError> {

View file

@ -0,0 +1,144 @@
import Foundation
import Postbox
import TelegramApi
import SyncCore
import SwiftSignalKit
public struct AuthTransferExportedToken {
public let value: Data
public let validUntil: Int32
}
public struct AuthTransferTokenInfo {
public let datacenterId: Int32
public let authKeyId: Int64
public let deviceModel: String
public let platform: String
public let systemVersion: String
public let apiId: Int32
public let appName: String
public let appVersion: String
public let ip: String
public let region: String
}
public enum ExportAuthTransferTokenError {
case generic
}
public enum ExportAuthTransferTokenResult {
case displayToken(AuthTransferExportedToken)
case changeAccountAndRetry(UnauthorizedAccount)
case loggedIn
}
public func exportAuthTransferToken(accountManager: AccountManager, account: UnauthorizedAccount, syncContacts: Bool) -> Signal<ExportAuthTransferTokenResult, ExportAuthTransferTokenError> {
return account.network.request(Api.functions.auth.exportLoginToken(apiId: account.networkArguments.apiId, apiHash: account.networkArguments.apiHash))
|> mapError { _ -> ExportAuthTransferTokenError in
return .generic
}
|> mapToSignal { result -> Signal<ExportAuthTransferTokenResult, ExportAuthTransferTokenError> in
switch result {
case let .loginToken(expires, token):
return .single(.displayToken(AuthTransferExportedToken(value: token.makeData(), validUntil: expires)))
case let .loginTokenMigrateTo(dcId, token):
let updatedAccount = account.changedMasterDatacenterId(accountManager: accountManager, masterDatacenterId: dcId)
return updatedAccount
|> castError(ExportAuthTransferTokenError.self)
|> mapToSignal { updatedAccount -> Signal<ExportAuthTransferTokenResult, ExportAuthTransferTokenError> in
return updatedAccount.network.request(Api.functions.auth.importLoginToken(token: token))
|> mapError { _ -> ExportAuthTransferTokenError in
return .generic
}
|> mapToSignal { result -> Signal<ExportAuthTransferTokenResult, ExportAuthTransferTokenError> in
switch result {
case let .loginTokenSuccess(authorization):
switch authorization {
case let .authorization(_, _, user):
return updatedAccount.postbox.transaction { transaction -> Signal<ExportAuthTransferTokenResult, ExportAuthTransferTokenError> in
let user = TelegramUser(user: user)
let state = AuthorizedAccountState(isTestingEnvironment: updatedAccount.testingEnvironment, masterDatacenterId: updatedAccount.masterDatacenterId, peerId: user.id, state: nil)
initializedAppSettingsAfterLogin(transaction: transaction, appVersion: updatedAccount.networkArguments.appVersion, syncContacts: syncContacts)
transaction.setState(state)
return accountManager.transaction { transaction -> ExportAuthTransferTokenResult in
switchToAuthorizedAccount(transaction: transaction, account: updatedAccount)
return .loggedIn
}
|> castError(ExportAuthTransferTokenError.self)
}
|> castError(ExportAuthTransferTokenError.self)
|> switchToLatest
default:
return .fail(.generic)
}
default:
return .single(.changeAccountAndRetry(updatedAccount))
}
}
}
case let .loginTokenSuccess(authorization):
switch authorization {
case let .authorization(_, _, user):
return account.postbox.transaction { transaction -> Signal<ExportAuthTransferTokenResult, ExportAuthTransferTokenError> in
let user = TelegramUser(user: user)
let state = AuthorizedAccountState(isTestingEnvironment: account.testingEnvironment, masterDatacenterId: account.masterDatacenterId, peerId: user.id, state: nil)
initializedAppSettingsAfterLogin(transaction: transaction, appVersion: account.networkArguments.appVersion, syncContacts: syncContacts)
transaction.setState(state)
return accountManager.transaction { transaction -> ExportAuthTransferTokenResult in
switchToAuthorizedAccount(transaction: transaction, account: account)
return .loggedIn
}
|> castError(ExportAuthTransferTokenError.self)
}
|> castError(ExportAuthTransferTokenError.self)
|> switchToLatest
case let .authorizationSignUpRequired:
return .fail(.generic)
}
}
}
}
public enum GetAuthTransferTokenInfoError {
case generic
case invalid
case expired
case alreadyAccepted
}
public func getAuthTransferTokenInfo(network: Network, token: Data) -> Signal<AuthTransferTokenInfo, GetAuthTransferTokenInfoError> {
return network.request(Api.functions.auth.checkLoginToken(token: Buffer(data: token)))
|> mapError { error -> GetAuthTransferTokenInfoError in
switch error.errorDescription {
case "AUTH_TOKEN_INVALID":
return .invalid
case "AUTH_TOKEN_EXPIRED":
return .expired
case "AUTH_TOKEN_ALREADY_ACCEPTED":
return .alreadyAccepted
default:
return .generic
}
}
|> map { result -> AuthTransferTokenInfo in
switch result {
case let .loginTokenInfo(dcId, authKeyId, deviceModel, platform, systemVersion, apiId, appName, appVersion, ip, region):
return AuthTransferTokenInfo(datacenterId: dcId, authKeyId: authKeyId, deviceModel: deviceModel, platform: platform, systemVersion: systemVersion, apiId: apiId, appName: appName, appVersion: appVersion, ip: ip, region: region)
}
}
}
public enum ApproveAuthTransferTokenError {
case generic
}
public func approveAuthTransferToken(account: Account, token: Data) -> Signal<Never, ApproveAuthTransferTokenError> {
return account.network.request(Api.functions.auth.acceptLoginToken(token: Buffer(data: token)))
|> mapError { _ -> ApproveAuthTransferTokenError in
return .generic
}
|> mapToSignal { updates -> Signal<Never, ApproveAuthTransferTokenError> in
account.stateManager.addUpdates(updates)
return .complete()
}
}

View file

@ -15,7 +15,7 @@ public enum AuthorizationCodeRequestError {
case timeout
}
private func switchToAuthorizedAccount(transaction: AccountManagerModifier, account: UnauthorizedAccount) {
func switchToAuthorizedAccount(transaction: AccountManagerModifier, account: UnauthorizedAccount) {
let nextSortOrder = (transaction.getRecords().map({ record -> Int32 in
for attribute in record.attributes {
if let attribute = attribute as? AccountSortOrderAttribute {

View file

@ -399,6 +399,7 @@ func networkUsageStats(basePath: String, reset: ResetNetworkUsageStats) -> Signa
public struct NetworkInitializationArguments {
public let apiId: Int32
public let apiHash: String
public let languagesCategory: String
public let appVersion: String
public let voipMaxLayer: Int32
@ -406,8 +407,9 @@ public struct NetworkInitializationArguments {
public let autolockDeadine: Signal<Int32?, NoError>
public let encryptionProvider: EncryptionProvider
public init(apiId: Int32, languagesCategory: String, appVersion: String, voipMaxLayer: Int32, appData: Signal<Data?, NoError>, autolockDeadine: Signal<Int32?, NoError>, encryptionProvider: EncryptionProvider) {
public init(apiId: Int32, apiHash: String, languagesCategory: String, appVersion: String, voipMaxLayer: Int32, appData: Signal<Data?, NoError>, autolockDeadine: Signal<Int32?, NoError>, encryptionProvider: EncryptionProvider) {
self.apiId = apiId
self.apiHash = apiHash
self.languagesCategory = languagesCategory
self.appVersion = appVersion
self.voipMaxLayer = voipMaxLayer

View file

@ -0,0 +1,86 @@
import Foundation
import Postbox
import SwiftSignalKit
import TelegramApi
import MtProtoKit
import SyncCore
private final class UnauthorizedUpdateMessageService: NSObject, MTMessageService {
let pipe: ValuePipe<[Api.Update]> = ValuePipe()
var mtProto: MTProto?
override init() {
super.init()
}
func mtProtoWillAdd(_ mtProto: MTProto!) {
self.mtProto = mtProto
}
func mtProtoDidChangeSession(_ mtProto: MTProto!) {
}
func mtProtoServerDidChangeSession(_ mtProto: MTProto!, firstValidMessageId: Int64, otherValidMessageIds: [Any]!) {
}
func putNext(_ updates: [Api.Update]) {
self.pipe.putNext(updates)
}
func mtProto(_ mtProto: MTProto!, receivedMessage message: MTIncomingMessage!) {
if let updates = (message.body as? BoxedMessage)?.body as? Api.Updates {
self.addUpdates(updates)
}
}
func addUpdates(_ updates: Api.Updates) {
switch updates {
case let .updates(updates, _, _, _, _):
self.putNext(updates)
case let .updatesCombined(updates, _, _, _, _, _):
self.putNext(updates)
case let .updateShort(update, _):
self.putNext([update])
case .updateShortChatMessage, .updateShortMessage, .updatesTooLong, .updateShortSentMessage:
break
}
}
}
final class UnauthorizedAccountStateManager {
private let queue = Queue()
private let network: Network
private var updateService: UnauthorizedUpdateMessageService?
private let updateServiceDisposable = MetaDisposable()
private let updateLoginToken: () -> Void
init(network: Network, updateLoginToken: @escaping () -> Void) {
self.network = network
self.updateLoginToken = updateLoginToken
}
deinit {
self.updateServiceDisposable.dispose()
}
func reset() {
self.queue.async {
if self.updateService == nil {
self.updateService = UnauthorizedUpdateMessageService()
let updateLoginToken = self.updateLoginToken
self.updateServiceDisposable.set(self.updateService!.pipe.signal().start(next: { updates in
for update in updates {
switch update {
case .updateLoginToken:
updateLoginToken()
default:
break
}
}
}))
self.network.mtProto.add(self.updateService)
}
}
}
}

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "EmojiComputer.pdf"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View file

@ -371,9 +371,10 @@ final class SharedApplicationContext {
let signatureDict = BuildConfigExtra.signatureDict()
let apiId: Int32 = buildConfig.apiId
let apiHash: String = buildConfig.apiHash
let languagesCategory = "ios"
let networkArguments = NetworkInitializationArguments(apiId: apiId, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: PresentationCallManagerImpl.voipMaxLayer, appData: self.deviceToken.get()
let networkArguments = NetworkInitializationArguments(apiId: apiId, apiHash: apiHash, languagesCategory: languagesCategory, appVersion: appVersion, voipMaxLayer: PresentationCallManagerImpl.voipMaxLayer, appData: self.deviceToken.get()
|> map { token in
let data = buildConfig.bundleData(withAppToken: token, signatureDict: signatureDict)
if let data = data, let jsonString = String(data: data, encoding: .utf8) {

View file

@ -144,7 +144,7 @@ public final class AuthorizationSequenceController: NavigationController, MFMail
if let currentController = currentController {
controller = currentController
} else {
controller = AuthorizationSequencePhoneEntryController(sharedContext: self.sharedContext, isTestingEnvironment: self.account.testingEnvironment, otherAccountPhoneNumbers: self.otherAccountPhoneNumbers, network: self.account.network, presentationData: self.presentationData, openUrl: { [weak self] url in
controller = AuthorizationSequencePhoneEntryController(sharedContext: self.sharedContext, account: self.account, isTestingEnvironment: self.account.testingEnvironment, otherAccountPhoneNumbers: self.otherAccountPhoneNumbers, network: self.account.network, presentationData: self.presentationData, openUrl: { [weak self] url in
self?.openUrl(url)
}, back: { [weak self] in
guard let strongSelf = self else {
@ -160,6 +160,12 @@ public final class AuthorizationSequenceController: NavigationController, MFMail
}).start()
}
})
controller.accountUpdated = { [weak self] updatedAccount in
guard let strongSelf = self else {
return
}
strongSelf.account = updatedAccount
}
controller.loginWithNumber = { [weak self, weak controller] number, syncContacts in
if let strongSelf = self {
controller?.inProgress = true

View file

@ -19,6 +19,7 @@ final class AuthorizationSequencePhoneEntryController: ViewController {
}
private let sharedContext: SharedAccountContext
private var account: UnauthorizedAccount
private let isTestingEnvironment: Bool
private let otherAccountPhoneNumbers: ((String, AccountRecordId, Bool)?, [(String, AccountRecordId, Bool)])
private let network: Network
@ -41,13 +42,15 @@ final class AuthorizationSequencePhoneEntryController: ViewController {
}
}
var loginWithNumber: ((String, Bool) -> Void)?
var accountUpdated: ((UnauthorizedAccount) -> Void)?
private let termsDisposable = MetaDisposable()
private let hapticFeedback = HapticFeedback()
init(sharedContext: SharedAccountContext, isTestingEnvironment: Bool, otherAccountPhoneNumbers: ((String, AccountRecordId, Bool)?, [(String, AccountRecordId, Bool)]), network: Network, presentationData: PresentationData, openUrl: @escaping (String) -> Void, back: @escaping () -> Void) {
init(sharedContext: SharedAccountContext, account: UnauthorizedAccount, isTestingEnvironment: Bool, otherAccountPhoneNumbers: ((String, AccountRecordId, Bool)?, [(String, AccountRecordId, Bool)]), network: Network, presentationData: PresentationData, openUrl: @escaping (String) -> Void, back: @escaping () -> Void) {
self.sharedContext = sharedContext
self.account = account
self.isTestingEnvironment = isTestingEnvironment
self.otherAccountPhoneNumbers = otherAccountPhoneNumbers
self.network = network
@ -95,13 +98,21 @@ final class AuthorizationSequencePhoneEntryController: ViewController {
}
override public func loadDisplayNode() {
self.displayNode = AuthorizationSequencePhoneEntryControllerNode(strings: self.presentationData.strings, theme: self.presentationData.theme, debugAction: { [weak self] in
self.displayNode = AuthorizationSequencePhoneEntryControllerNode(accountManager: self.sharedContext.accountManager, account: self.account, strings: self.presentationData.strings, theme: self.presentationData.theme, debugAction: { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.view.endEditing(true)
self?.present(debugController(sharedContext: strongSelf.sharedContext, context: nil, modal: true), in: .window(.root), with: ViewControllerPresentationArguments(presentationAnimation: .modalSheet))
}, hasOtherAccounts: self.otherAccountPhoneNumbers.0 != nil)
}, hasOtherAccounts: self.otherAccountPhoneNumbers.0 != nil)
self.controllerNode.accountUpdated = { [weak self] account in
guard let strongSelf = self else {
return
}
strongSelf.account = account
strongSelf.accountUpdated?(account)
}
if let (code, name, number) = self.currentData {
self.controllerNode.codeAndNumber = (code, name, number)
}

View file

@ -8,6 +8,9 @@ import TelegramPresentationData
import PhoneInputNode
import CountrySelectionUI
import AuthorizationUI
import QrCode
import SwiftSignalKit
import Postbox
private func emojiFlagForISOCountryCode(_ countryCode: NSString) -> String {
if countryCode.length != 2 {
@ -201,6 +204,8 @@ private final class ContactSyncNode: ASDisplayNode {
}
final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
private let accountManager: AccountManager
private var account: UnauthorizedAccount
private let strings: PresentationStrings
private let theme: PresentationTheme
private let hasOtherAccounts: Bool
@ -210,6 +215,11 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
private let phoneAndCountryNode: PhoneAndCountryNode
private let contactSyncNode: ContactSyncNode
private var qrNode: ASImageNode?
private let exportTokenDisposable = MetaDisposable()
private let tokenEventsDisposable = MetaDisposable()
var accountUpdated: ((UnauthorizedAccount) -> Void)?
private let debugAction: () -> Void
var currentNumber: String {
@ -245,7 +255,10 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
}
}
init(strings: PresentationStrings, theme: PresentationTheme, debugAction: @escaping () -> Void, hasOtherAccounts: Bool) {
init(accountManager: AccountManager, account: UnauthorizedAccount, strings: PresentationStrings, theme: PresentationTheme, debugAction: @escaping () -> Void, hasOtherAccounts: Bool) {
self.accountManager = accountManager
self.account = account
self.strings = strings
self.theme = theme
self.debugAction = debugAction
@ -257,7 +270,7 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
self.titleNode.attributedText = NSAttributedString(string: strings.Login_PhoneTitle, font: Font.light(30.0), textColor: theme.list.itemPrimaryTextColor)
self.noticeNode = ASTextNode()
self.noticeNode.isUserInteractionEnabled = false
self.noticeNode.isUserInteractionEnabled = true
self.noticeNode.displaysAsynchronously = false
self.noticeNode.attributedText = NSAttributedString(string: strings.Login_PhoneAndCountryHelp, font: Font.regular(16.0), textColor: theme.list.itemPrimaryTextColor, paragraphAlignment: .center)
@ -285,12 +298,23 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
self.phoneAndCountryNode.checkPhone = { [weak self] in
self?.checkPhone?()
}
self.tokenEventsDisposable.set((account.updateLoginTokenEvents
|> deliverOnMainQueue).start(next: { [weak self] _ in
self?.refreshQrToken()
}))
}
deinit {
self.exportTokenDisposable.dispose()
self.tokenEventsDisposable.dispose()
}
override func didLoad() {
super.didLoad()
self.titleNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.debugTap(_:))))
self.noticeNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.debugQrTap(_:))))
}
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
@ -356,4 +380,66 @@ final class AuthorizationSequencePhoneEntryControllerNode: ASDisplayNode {
}
}
}
@objc private func debugQrTap(_ recognizer: UITapGestureRecognizer) {
if self.qrNode == nil {
let qrNode = ASImageNode()
qrNode.frame = CGRect(origin: CGPoint(x: 16.0, y: 64.0 + 16.0), size: CGSize(width: 200.0, height: 200.0))
self.qrNode = qrNode
self.addSubnode(qrNode)
self.refreshQrToken()
}
}
private func refreshQrToken() {
let tokenSignal = exportAuthTransferToken(accountManager: self.accountManager, account: self.account, syncContacts: true)
self.exportTokenDisposable.set((tokenSignal
|> deliverOnMainQueue).start(next: { [weak self] result in
guard let strongSelf = self else {
return
}
switch result {
case let .displayToken(token):
var tokenString = token.value.base64EncodedString()
print("export token \(tokenString)")
tokenString = tokenString.replacingOccurrences(of: "+", with: "-")
tokenString = tokenString.replacingOccurrences(of: "/", with: "_")
let urlString = "tg://login?token=\(tokenString)"
let _ = (qrCode(string: urlString, color: .black, backgroundColor: .white, icon: .none)
|> deliverOnMainQueue).start(next: { _, generate in
guard let strongSelf = self else {
return
}
let context = generate(TransformImageArguments(corners: ImageCorners(), imageSize: CGSize(width: 200.0, height: 200.0), boundingSize: CGSize(width: 200.0, height: 200.0), intrinsicInsets: UIEdgeInsets()))
if let image = context?.generateImage() {
strongSelf.qrNode?.image = image
}
})
let timestamp = Int32(Date().timeIntervalSince1970)
let timeout = max(5, token.validUntil - timestamp)
strongSelf.exportTokenDisposable.set((Signal<Never, NoError>.complete()
|> delay(Double(timeout), queue: .mainQueue())).start(completed: {
guard let strongSelf = self else {
return
}
strongSelf.refreshQrToken()
}))
case let .changeAccountAndRetry(account):
strongSelf.exportTokenDisposable.set(nil)
strongSelf.account = account
strongSelf.accountUpdated?(account)
strongSelf.tokenEventsDisposable.set((account.updateLoginTokenEvents
|> deliverOnMainQueue).start(next: { _ in
self?.refreshQrToken()
}))
strongSelf.refreshQrToken()
case .loggedIn:
strongSelf.exportTokenDisposable.set(nil)
}
}))
}
}

View file

@ -188,7 +188,7 @@ private func mappedInsertEntries(context: AccountContext, chatLocation: ChatLoca
case .bubbles:
item = ChatMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes))
case let .list(search, _):
item = ListMessageItem(theme: presentationData.theme.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, context: context, chatLocation: chatLocation, controllerInteraction: controllerInteraction, message: message, selection: selection, displayHeader: search)
item = ListMessageItem(theme: presentationData.theme.theme, strings: presentationData.strings, fontSize: presentationData.fontSize, dateTimeFormat: presentationData.dateTimeFormat, context: context, chatLocation: chatLocation, controllerInteraction: controllerInteraction, message: message, selection: selection, displayHeader: search)
}
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint)
case let .MessageGroupEntry(_, messages, presentationData):
@ -198,7 +198,7 @@ private func mappedInsertEntries(context: AccountContext, chatLocation: ChatLoca
item = ChatMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .group(messages: messages))
case let .list(search, _):
assertionFailure()
item = ListMessageItem(theme: presentationData.theme.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, context: context, chatLocation: chatLocation, controllerInteraction: controllerInteraction, message: messages[0].0, selection: .none, displayHeader: search)
item = ListMessageItem(theme: presentationData.theme.theme, strings: presentationData.strings, fontSize: presentationData.fontSize, dateTimeFormat: presentationData.dateTimeFormat, context: context, chatLocation: chatLocation, controllerInteraction: controllerInteraction, message: messages[0].0, selection: .none, displayHeader: search)
}
return ListViewInsertItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint)
case let .UnreadEntry(_, presentationData):
@ -222,7 +222,7 @@ private func mappedUpdateEntries(context: AccountContext, chatLocation: ChatLoca
case .bubbles:
item = ChatMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes))
case let .list(search, _):
item = ListMessageItem(theme: presentationData.theme.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, context: context, chatLocation: chatLocation, controllerInteraction: controllerInteraction, message: message, selection: selection, displayHeader: search)
item = ListMessageItem(theme: presentationData.theme.theme, strings: presentationData.strings, fontSize: presentationData.fontSize, dateTimeFormat: presentationData.dateTimeFormat, context: context, chatLocation: chatLocation, controllerInteraction: controllerInteraction, message: message, selection: selection, displayHeader: search)
}
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint)
case let .MessageGroupEntry(_, messages, presentationData):
@ -232,7 +232,7 @@ private func mappedUpdateEntries(context: AccountContext, chatLocation: ChatLoca
item = ChatMessageItem(presentationData: presentationData, context: context, chatLocation: chatLocation, associatedData: associatedData, controllerInteraction: controllerInteraction, content: .group(messages: messages))
case let .list(search, _):
assertionFailure()
item = ListMessageItem(theme: presentationData.theme.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, context: context, chatLocation: chatLocation, controllerInteraction: controllerInteraction, message: messages[0].0, selection: .none, displayHeader: search)
item = ListMessageItem(theme: presentationData.theme.theme, strings: presentationData.strings, fontSize: presentationData.fontSize, dateTimeFormat: presentationData.dateTimeFormat, context: context, chatLocation: chatLocation, controllerInteraction: controllerInteraction, message: messages[0].0, selection: .none, displayHeader: search)
}
return ListViewUpdateItem(index: entry.index, previousIndex: entry.previousIndex, item: item, directionHint: entry.directionHint)
case let .UnreadEntry(_, presentationData):
@ -1547,7 +1547,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
case .bubbles:
item = ChatMessageItem(presentationData: presentationData, context: self.context, chatLocation: self.chatLocation, associatedData: associatedData, controllerInteraction: self.controllerInteraction, content: .message(message: message, read: read, selection: selection, attributes: attributes))
case let .list(search, _):
item = ListMessageItem(theme: presentationData.theme.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, context: self.context, chatLocation: self.chatLocation, controllerInteraction: self.controllerInteraction, message: message, selection: selection, displayHeader: search)
item = ListMessageItem(theme: presentationData.theme.theme, strings: presentationData.strings, fontSize: presentationData.fontSize, dateTimeFormat: presentationData.dateTimeFormat, context: self.context, chatLocation: self.chatLocation, controllerInteraction: self.controllerInteraction, message: message, selection: selection, displayHeader: search)
}
let updateItem = ListViewUpdateItem(index: index, previousIndex: index, item: item, directionHint: nil)
self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [updateItem], options: [.AnimateInsertion], scrollToItem: nil, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in })

View file

@ -10,6 +10,7 @@ import TelegramPresentationData
import MergeLists
import AccountContext
import SearchUI
import TelegramUIPreferences
private enum ChatHistorySearchEntryStableId: Hashable {
case messageId(MessageId)
@ -35,19 +36,19 @@ private enum ChatHistorySearchEntryStableId: Hashable {
private enum ChatHistorySearchEntry: Comparable, Identifiable {
case message(Message, PresentationTheme, PresentationStrings, PresentationDateTimeFormat)
case message(Message, PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationFontSize)
var stableId: ChatHistorySearchEntryStableId {
switch self {
case let .message(message, _, _, _):
case let .message(message, _, _, _, _):
return .messageId(message.id)
}
}
static func ==(lhs: ChatHistorySearchEntry, rhs: ChatHistorySearchEntry) -> Bool {
switch lhs {
case let .message(lhsMessage, lhsTheme, lhsStrings, lhsDateTimeFormat):
if case let .message(rhsMessage, rhsTheme, rhsStrings, rhsDateTimeFormat) = rhs {
case let .message(lhsMessage, lhsTheme, lhsStrings, lhsDateTimeFormat, lhsFontSize):
if case let .message(rhsMessage, rhsTheme, rhsStrings, rhsDateTimeFormat, rhsFontSize) = rhs {
if lhsMessage.id != rhsMessage.id {
return false
}
@ -63,6 +64,9 @@ private enum ChatHistorySearchEntry: Comparable, Identifiable {
if lhsDateTimeFormat != rhsDateTimeFormat {
return false
}
if lhsFontSize != rhsFontSize {
return false
}
return true
} else {
return false
@ -72,8 +76,8 @@ private enum ChatHistorySearchEntry: Comparable, Identifiable {
static func <(lhs: ChatHistorySearchEntry, rhs: ChatHistorySearchEntry) -> Bool {
switch lhs {
case let .message(lhsMessage, _, _, _):
if case let .message(rhsMessage, _, _, _) = rhs {
case let .message(lhsMessage, _, _, _, _):
if case let .message(rhsMessage, _, _, _, _) = rhs {
return lhsMessage.index < rhsMessage.index
} else {
return false
@ -83,8 +87,8 @@ private enum ChatHistorySearchEntry: Comparable, Identifiable {
func item(context: AccountContext, peerId: PeerId, interaction: ChatControllerInteraction) -> ListViewItem {
switch self {
case let .message(message, theme, strings, dateTimeFormat):
return ListMessageItem(theme: theme, strings: strings, dateTimeFormat: dateTimeFormat, context: context, chatLocation: .peer(peerId), controllerInteraction: interaction, message: message, selection: .none, displayHeader: true)
case let .message(message, theme, strings, dateTimeFormat, fontSize):
return ListMessageItem(theme: theme, strings: strings, fontSize: fontSize, dateTimeFormat: dateTimeFormat, context: context, chatLocation: .peer(peerId), controllerInteraction: interaction, message: message, selection: .none, displayHeader: true)
}
}
}
@ -134,7 +138,7 @@ final class ChatHistorySearchContainerNode: SearchDisplayControllerContentNode {
private var presentationData: PresentationData
private var presentationDataDisposable: Disposable?
private let themeAndStringsPromise: Promise<(PresentationTheme, PresentationStrings, PresentationDateTimeFormat)>
private let themeAndStringsPromise: Promise<(PresentationTheme, PresentationStrings, PresentationDateTimeFormat, PresentationFontSize)>
private var enqueuedTransitions: [(ChatHistorySearchContainerTransition, Bool)] = []
@ -143,7 +147,7 @@ final class ChatHistorySearchContainerNode: SearchDisplayControllerContentNode {
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
self.themeAndStringsPromise = Promise((self.presentationData.theme, self.presentationData.strings, self.presentationData.dateTimeFormat))
self.themeAndStringsPromise = Promise((self.presentationData.theme, self.presentationData.strings, self.presentationData.dateTimeFormat, self.presentationData.fontSize))
self.dimNode = ASDisplayNode()
self.dimNode.backgroundColor = UIColor.black.withAlphaComponent(0.5)
@ -191,7 +195,7 @@ final class ChatHistorySearchContainerNode: SearchDisplayControllerContentNode {
return ([], [:])
} else {
return (messages.map { message -> ChatHistorySearchEntry in
return .message(message, themeAndStrings.0, themeAndStrings.1, themeAndStrings.2)
return .message(message, themeAndStrings.0, themeAndStrings.1, themeAndStrings.2, themeAndStrings.3)
}, Dictionary(messages.map { ($0.id, $0) }, uniquingKeysWith: { lhs, _ in lhs }))
}
}
@ -224,7 +228,7 @@ final class ChatHistorySearchContainerNode: SearchDisplayControllerContentNode {
self.presentationDataDisposable = context.sharedContext.presentationData.start(next: { [weak self] presentationData in
if let strongSelf = self {
strongSelf.themeAndStringsPromise.set(.single((presentationData.theme, presentationData.strings, presentationData.dateTimeFormat)))
strongSelf.themeAndStringsPromise.set(.single((presentationData.theme, presentationData.strings, presentationData.dateTimeFormat, presentationData.fontSize)))
strongSelf.emptyResultsTitleNode.attributedText = NSAttributedString(string: presentationData.strings.SharedMedia_SearchNoResults, font: Font.semibold(17.0), textColor: presentationData.theme.list.freeTextColor, paragraphAlignment: .center)
@ -342,10 +346,10 @@ final class ChatHistorySearchContainerNode: SearchDisplayControllerContentNode {
if let currentEntries = self.currentEntries {
for entry in currentEntries {
switch entry {
case let .message(message, _, _, _):
if message.id == id {
return message
}
case let .message(message, _, _, _, _):
if message.id == id {
return message
}
}
}
}

View file

@ -449,6 +449,12 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
}
return CGPoint(x: strongSelf.backgroundNode.frame.maxX, y: strongSelf.backgroundNode.frame.minY)
}
reactionRecognizer.shouldElevateAnchorPoint = { [weak self] in
guard let strongSelf = self, let item = strongSelf.item else {
return false
}
return item.controllerInteraction.canSetupReply(item.message)
}
reactionRecognizer.began = { [weak self] in
guard let strongSelf = self, let item = strongSelf.item else {
return
@ -493,6 +499,9 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode
guard let strongSelf = self, let item = strongSelf.item else {
return
}
if !item.controllerInteraction.canSetupReply(item.message) {
return
}
if strongSelf.swipeToReplyFeedback == nil {
strongSelf.swipeToReplyFeedback = HapticFeedback()
}

View file

@ -127,6 +127,7 @@ final class HorizontalListContextResultsChatInputContextPanelNode: ChatInputCont
super.didLoad()
self.listView.view.disablesInteractiveTransitionGestureRecognizer = true
self.listView.view.disablesInteractiveKeyboardGestureRecognizer = true
self.view.addGestureRecognizer(PeekControllerGestureRecognizer(contentAtPoint: { [weak self] point in
if let strongSelf = self {
let convertedPoint = strongSelf.listView.view.convert(point, from: strongSelf.view)

View file

@ -114,10 +114,6 @@ private func extensionImage(fileExtension: String?) -> UIImage? {
return nil
}
}
private let titleFont = Font.medium(16.0)
private let audioTitleFont = Font.regular(16.0)
private let descriptionFont = Font.regular(13.0)
private let extensionFont = Font.medium(13.0)
private struct FetchControls {
@ -316,6 +312,10 @@ final class ListMessageFileItemNode: ListMessageNode {
updatedTheme = item.theme
}
let titleFont = Font.medium(floor(item.fontSize.baseDisplaySize * 16.0 / 17.0))
let audioTitleFont = Font.regular(floor(item.fontSize.baseDisplaySize * 16.0 / 17.0))
let descriptionFont = Font.regular(floor(item.fontSize.baseDisplaySize * 13.0 / 17.0))
var leftInset: CGFloat = 65.0 + params.leftInset
let rightInset: CGFloat = 8.0 + params.rightInset
@ -494,7 +494,7 @@ final class ListMessageFileItemNode: ListMessageNode {
insets.top += header.height
}
let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 56.0), insets: insets)
let nodeLayout = ListViewItemNodeLayout(contentSize: CGSize(width: params.width, height: 8.0 * 2.0 + titleNodeLayout.size.height + 3.0 + descriptionNodeLayout.size.height), insets: insets)
return (nodeLayout, { animation in
if let strongSelf = self {
@ -562,7 +562,7 @@ final class ListMessageFileItemNode: ListMessageNode {
}
}
transition.updateFrame(node: strongSelf.descriptionNode, frame: CGRect(origin: CGPoint(x: leftOffset + leftInset + descriptionOffset, y: 32.0), size: descriptionNodeLayout.size))
transition.updateFrame(node: strongSelf.descriptionNode, frame: CGRect(origin: CGPoint(x: leftOffset + leftInset + descriptionOffset, y: strongSelf.titleNode.frame.maxY + 3.0), size: descriptionNodeLayout.size))
let _ = descriptionNodeApply()
let iconFrame: CGRect
@ -630,7 +630,7 @@ final class ListMessageFileItemNode: ListMessageNode {
}))
}
transition.updateFrame(node: strongSelf.downloadStatusIconNode, frame: CGRect(origin: CGPoint(x: leftOffset + leftInset, y: 34.0), size: CGSize(width: 11.0, height: 11.0)))
transition.updateFrame(node: strongSelf.downloadStatusIconNode, frame: CGRect(origin: CGPoint(x: leftOffset + leftInset, y: strongSelf.descriptionNode.frame.minY + floor((strongSelf.descriptionNode.frame.height - 11.0) / 2.0)), size: CGSize(width: 11.0, height: 11.0)))
if let updatedFetchControls = updatedFetchControls {
let _ = strongSelf.fetchControls.swap(updatedFetchControls)
@ -840,6 +840,7 @@ final class ListMessageFileItemNode: ListMessageNode {
self.descriptionProgressNode.isHidden = true
self.descriptionNode.isHidden = false
}
let descriptionFont = Font.regular(floor(item.fontSize.baseDisplaySize * 13.0 / 17.0))
self.descriptionProgressNode.attributedText = NSAttributedString(string: downloadingString ?? "", font: descriptionFont, textColor: item.theme.list.itemSecondaryTextColor)
let descriptionSize = self.descriptionProgressNode.updateLayout(CGSize(width: size.width - 14.0, height: size.height))
transition.updateFrame(node: self.descriptionProgressNode, frame: CGRect(origin: self.descriptionNode.frame.origin, size: descriptionSize))

View file

@ -8,10 +8,12 @@ import SwiftSignalKit
import Postbox
import TelegramPresentationData
import AccountContext
import TelegramUIPreferences
final class ListMessageItem: ListViewItem {
let theme: PresentationTheme
let strings: PresentationStrings
let fontSize: PresentationFontSize
let dateTimeFormat: PresentationDateTimeFormat
let context: AccountContext
let chatLocation: ChatLocation
@ -23,9 +25,10 @@ final class ListMessageItem: ListViewItem {
let selectable: Bool = true
public init(theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, context: AccountContext, chatLocation: ChatLocation, controllerInteraction: ChatControllerInteraction, message: Message, selection: ChatHistoryMessageSelection, displayHeader: Bool) {
public init(theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, dateTimeFormat: PresentationDateTimeFormat, context: AccountContext, chatLocation: ChatLocation, controllerInteraction: ChatControllerInteraction, message: Message, selection: ChatHistoryMessageSelection, displayHeader: Bool) {
self.theme = theme
self.strings = strings
self.fontSize = fontSize
self.dateTimeFormat = dateTimeFormat
self.context = context
self.chatLocation = chatLocation

View file

@ -14,8 +14,6 @@ import PhotoResources
import WebsiteType
import UrlHandling
private let titleFont = Font.medium(16.0)
private let descriptionFont = Font.regular(14.0)
private let iconFont = Font.medium(22.0)
private let iconTextBackgroundImage = generateStretchableFilledCircleImage(radius: 2.0, color: UIColor(rgb: 0xdfdfdf))
@ -157,6 +155,9 @@ final class ListMessageSnippetItemNode: ListMessageNode {
updatedTheme = item.theme
}
let titleFont = Font.medium(floor(item.fontSize.baseDisplaySize * 16.0 / 17.0))
let descriptionFont = Font.regular(floor(item.fontSize.baseDisplaySize * 14.0 / 17.0))
let leftInset: CGFloat = 65.0 + params.leftInset
var leftOffset: CGFloat = 0.0
@ -327,7 +328,7 @@ final class ListMessageSnippetItemNode: ListMessageNode {
}
}
let contentHeight = 40.0 + descriptionNodeLayout.size.height + linkNodeLayout.size.height
let contentHeight = 9.0 + titleNodeLayout.size.height + 10.0 + descriptionNodeLayout.size.height + linkNodeLayout.size.height
var insets = UIEdgeInsets()
if dateHeaderAtBottom, let header = item.header {
@ -379,7 +380,7 @@ final class ListMessageSnippetItemNode: ListMessageNode {
transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftOffset + leftInset, y: 9.0), size: titleNodeLayout.size))
let _ = titleNodeApply()
let descriptionFrame = CGRect(origin: CGPoint(x: leftOffset + leftInset - 1.0, y: 32.0), size: descriptionNodeLayout.size)
let descriptionFrame = CGRect(origin: CGPoint(x: leftOffset + leftInset - 1.0, y: strongSelf.titleNode.frame.maxY + 3.0), size: descriptionNodeLayout.size)
transition.updateFrame(node: strongSelf.descriptionNode, frame: descriptionFrame)
let _ = descriptionNodeApply()

View file

@ -55,14 +55,16 @@ private func parseFileLocationResource(_ dict: [AnyHashable: Any]) -> TelegramMe
public struct NotificationViewControllerInitializationData {
public let appGroupPath: String
public let apiId: Int32
public let apiHash: String
public let languagesCategory: String
public let encryptionParameters: (Data, Data)
public let appVersion: String
public let bundleData: Data?
public init(appGroupPath: String, apiId: Int32, languagesCategory: String, encryptionParameters: (Data, Data), appVersion: String, bundleData: Data?) {
public init(appGroupPath: String, apiId: Int32, apiHash: String, languagesCategory: String, encryptionParameters: (Data, Data), appVersion: String, bundleData: Data?) {
self.appGroupPath = appGroupPath
self.apiId = apiId
self.apiHash = apiHash
self.languagesCategory = languagesCategory
self.encryptionParameters = encryptionParameters
self.appVersion = appVersion
@ -152,7 +154,7 @@ public final class NotificationViewControllerImpl {
return nil
})
sharedAccountContext = SharedAccountContextImpl(mainWindow: nil, basePath: rootPath, encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: self.initializationData.encryptionParameters.0)!, salt: ValueBoxEncryptionParameters.Salt(data: self.initializationData.encryptionParameters.1)!), accountManager: accountManager, appLockContext: appLockContext, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: self.initializationData.apiId, languagesCategory: self.initializationData.languagesCategory, appVersion: self.initializationData.appVersion, voipMaxLayer: 0, appData: .single(self.initializationData.bundleData), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider()), rootPath: rootPath, legacyBasePath: nil, legacyCache: nil, apsNotificationToken: .never(), voipNotificationToken: .never(), setNotificationCall: { _ in }, navigateToChat: { _, _, _ in })
sharedAccountContext = SharedAccountContextImpl(mainWindow: nil, basePath: rootPath, encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: self.initializationData.encryptionParameters.0)!, salt: ValueBoxEncryptionParameters.Salt(data: self.initializationData.encryptionParameters.1)!), accountManager: accountManager, appLockContext: appLockContext, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: self.initializationData.apiId, apiHash: self.initializationData.apiHash, languagesCategory: self.initializationData.languagesCategory, appVersion: self.initializationData.appVersion, voipMaxLayer: 0, appData: .single(self.initializationData.bundleData), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider()), rootPath: rootPath, legacyBasePath: nil, legacyCache: nil, apsNotificationToken: .never(), voipNotificationToken: .never(), setNotificationCall: { _ in }, navigateToChat: { _, _, _ in })
presentationDataPromise.set(sharedAccountContext!.presentationData)
}

View file

@ -48,14 +48,16 @@ private enum ShareAuthorizationError {
public struct ShareRootControllerInitializationData {
public let appGroupPath: String
public let apiId: Int32
public let apiHash: String
public let languagesCategory: String
public let encryptionParameters: (Data, Data)
public let appVersion: String
public let bundleData: Data?
public init(appGroupPath: String, apiId: Int32, languagesCategory: String, encryptionParameters: (Data, Data), appVersion: String, bundleData: Data?) {
public init(appGroupPath: String, apiId: Int32, apiHash: String, languagesCategory: String, encryptionParameters: (Data, Data), appVersion: String, bundleData: Data?) {
self.appGroupPath = appGroupPath
self.apiId = apiId
self.apiHash = apiHash
self.languagesCategory = languagesCategory
self.encryptionParameters = encryptionParameters
self.appVersion = appVersion
@ -190,7 +192,7 @@ public class ShareRootControllerImpl {
return nil
})
let sharedContext = SharedAccountContextImpl(mainWindow: nil, basePath: rootPath, encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: self.initializationData.encryptionParameters.0)!, salt: ValueBoxEncryptionParameters.Salt(data: self.initializationData.encryptionParameters.1)!), accountManager: accountManager, appLockContext: appLockContext, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: self.initializationData.apiId, languagesCategory: self.initializationData.languagesCategory, appVersion: self.initializationData.appVersion, voipMaxLayer: 0, appData: .single(self.initializationData.bundleData), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider()), rootPath: rootPath, legacyBasePath: nil, legacyCache: nil, apsNotificationToken: .never(), voipNotificationToken: .never(), setNotificationCall: { _ in }, navigateToChat: { _, _, _ in })
let sharedContext = SharedAccountContextImpl(mainWindow: nil, basePath: rootPath, encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: self.initializationData.encryptionParameters.0)!, salt: ValueBoxEncryptionParameters.Salt(data: self.initializationData.encryptionParameters.1)!), accountManager: accountManager, appLockContext: appLockContext, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: self.initializationData.apiId, apiHash: self.initializationData.apiHash, languagesCategory: self.initializationData.languagesCategory, appVersion: self.initializationData.appVersion, voipMaxLayer: 0, appData: .single(self.initializationData.bundleData), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider()), rootPath: rootPath, legacyBasePath: nil, legacyCache: nil, apsNotificationToken: .never(), voipNotificationToken: .never(), setNotificationCall: { _ in }, navigateToChat: { _, _, _ in })
presentationDataPromise.set(sharedContext.presentationData)
internalContext = InternalContext(sharedContext: sharedContext)
globalInternalContext = internalContext

View file

@ -448,12 +448,12 @@ public final class WalletStrings: Equatable {
public var Wallet_SecureStorageReset_Title: String { return self._s[218]! }
public var Wallet_Receive_CommentHeader: String { return self._s[219]! }
public var Wallet_Info_ReceiveGrams: String { return self._s[220]! }
public func Wallet_Updated_HoursAgo(_ value: Int32) -> String {
public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String {
let form = getPluralizationForm(self.lc, value)
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
return String(format: self._ps[0 * 6 + Int(form.rawValue)]!, stringValue)
}
public func Wallet_Updated_MinutesAgo(_ value: Int32) -> String {
public func Wallet_Updated_HoursAgo(_ value: Int32) -> String {
let form = getPluralizationForm(self.lc, value)
let stringValue = walletStringsFormattedNumber(value, self.groupingSeparator)
return String(format: self._ps[1 * 6 + Int(form.rawValue)]!, stringValue)

View file

@ -163,7 +163,7 @@ def is_plist(file1):
def diff_plists(file1, file2):
remove_properties = ['UISupportedDevices', 'DTAppStoreToolsBuild', 'MinimumOSVersion', 'BuildMachineOSBuild']
remove_properties = ['UISupportedDevices', 'DTAppStoreToolsBuild', 'MinimumOSVersion', 'BuildMachineOSBuild', 'CFBundleVersion']
clean1_properties = ''
clean2_properties = ''
@ -181,11 +181,6 @@ def diff_plists(file1, file2):
if data1 == data2:
return 'equal'
else:
with open('lhs.plist', 'wb') as f:
f.write(str.encode(data1))
with open('rhs.plist', 'wb') as f:
f.write(str.encode(data2))
sys.exit(1)
return 'not_equal'
@ -285,6 +280,7 @@ def ipadiff(self_base_path, ipa1, ipa2):
print('Different files in ' + ipa1 + ' and ' + ipa2)
for relative_file_path in different_files:
print(' ' + relative_file_path)
sys.exit(1)
else:
if len(encrypted_files) != 0 or len(watch_ipa1_files) != 0 or len(plugin_ipa1_files) != 0:
print('IPAs are equal, except for the files that can\'t currently be checked:')

View file

@ -175,16 +175,54 @@ static void writeDataToFile(std::vector<uint8_t> const &data, std::string const
static std::vector<uint8_t> stripSwiftSymbols(std::string const &file) {
std::string command;
command += "xcrun strip -ST -o /dev/stdout \"";
command += "xcrun bitcode_strip \"";
command += file;
command += "\" 2> /dev/null";
command += "\" -r -o \"";
command += file;
command += ".stripped\"";
uint8_t buffer[128];
std::vector<uint8_t> result;
FILE *pipe = popen(command.c_str(), "r");
if (!pipe) {
throw std::runtime_error("popen() failed!");
}
while (true) {
size_t readBytes = fread(buffer, 1, 128, pipe);
if (readBytes <= 0) {
break;
}
}
pclose(pipe);
command = "";
command += "codesign --remove-signature \"";
command += file;
command += ".stripped\"";
pipe = popen(command.c_str(), "r");
if (!pipe) {
throw std::runtime_error("popen() failed!");
}
while (true) {
size_t readBytes = fread(buffer, 1, 128, pipe);
if (readBytes <= 0) {
break;
}
}
pclose(pipe);
command = "";
command += "xcrun strip -ST -o /dev/stdout \"";
command += file;
command += ".stripped\" 2> /dev/null";
std::vector<uint8_t> result;
pipe = popen(command.c_str(), "r");
if (!pipe) {
throw std::runtime_error("popen() failed!");
}
while (true) {
size_t readBytes = fread(buffer, 1, 128, pipe);
if (readBytes <= 0) {
@ -193,6 +231,23 @@ static std::vector<uint8_t> stripSwiftSymbols(std::string const &file) {
result.insert(result.end(), buffer, buffer + readBytes);
}
pclose(pipe);
command = "";
command += "rm \"";
command += file;
command += ".stripped\"";
pipe = popen(command.c_str(), "r");
if (!pipe) {
throw std::runtime_error("popen() failed!");
}
while (true) {
size_t readBytes = fread(buffer, 1, 128, pipe);
if (readBytes <= 0) {
break;
}
}
pclose(pipe);
return result;
}