mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
91fb33f71e
73 changed files with 5089 additions and 3454 deletions
|
|
@ -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
|
||||
|
|
|
|||
4
Makefile
4
Makefile
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -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
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
51
build-system/verify.sh
Normal 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"
|
||||
|
||||
$@
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
BIN
buildbox/fake-codesigning/certs/distribution/SelfSigned.p12
Normal file
BIN
buildbox/fake-codesigning/certs/distribution/SelfSigned.p12
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
2
buildbox/generate_fake_codesigning.sh
Normal file
2
buildbox/generate_fake_codesigning.sh
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/bash
|
||||
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
53
buildbox/verify-telegram.sh
Normal file
53
buildbox/verify-telegram.sh
Normal 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
|
||||
|
||||
|
||||
28
submodules/AuthTransferUI/BUCK
Normal file
28
submodules/AuthTransferUI/BUCK
Normal 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",
|
||||
],
|
||||
)
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
448
submodules/AuthTransferUI/Sources/AuthTransferScanScreen.swift
Normal file
448
submodules/AuthTransferUI/Sources/AuthTransferScanScreen.swift
Normal 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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>) {
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
144
submodules/TelegramCore/Sources/AuthTransfer.swift
Normal file
144
submodules/TelegramCore/Sources/AuthTransfer.swift
Normal 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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
12
submodules/TelegramUI/Images.xcassets/Settings/TransferAuthLaptop.imageset/Contents.json
vendored
Normal file
12
submodules/TelegramUI/Images.xcassets/Settings/TransferAuthLaptop.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "EmojiComputer.pdf"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
submodules/TelegramUI/Images.xcassets/Settings/TransferAuthLaptop.imageset/EmojiComputer.pdf
vendored
Normal file
BIN
submodules/TelegramUI/Images.xcassets/Settings/TransferAuthLaptop.imageset/EmojiComputer.pdf
vendored
Normal file
Binary file not shown.
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 })
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -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
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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:')
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue