mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-15 13:14:10 +00:00
Merge commit '2485d6d99787b50e9d8b6fef0ca2768c930a9076' into experimental-2
This commit is contained in:
commit
8119f8c234
139
.github/workflows/build.yml
vendored
Normal file
139
.github/workflows/build.yml
vendored
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: macos-10.15
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: 'recursive'
|
||||||
|
fetch-depth: '0'
|
||||||
|
|
||||||
|
- name: Set active Xcode path
|
||||||
|
run: sudo xcode-select -s /Applications/Xcode_12.3.app/Contents/Developer
|
||||||
|
|
||||||
|
- name: Create canonical source directory
|
||||||
|
run: |
|
||||||
|
set -x
|
||||||
|
sudo mkdir /Users/telegram
|
||||||
|
sudo chown -R $(whoami) /Users/telegram
|
||||||
|
cp -R $GITHUB_WORKSPACE /Users/telegram/
|
||||||
|
mv /Users/telegram/$(basename $GITHUB_WORKSPACE) /Users/telegram/telegram-ios
|
||||||
|
|
||||||
|
- name: Build the App
|
||||||
|
run: |
|
||||||
|
set -x
|
||||||
|
|
||||||
|
# source code paths are included in the final binary, so we need to make them stable across builds
|
||||||
|
SOURCE_DIR=/Users/telegram/telegram-ios
|
||||||
|
|
||||||
|
# use canonical bazel root
|
||||||
|
BAZEL_USER_ROOT="/private/var/tmp/_bazel_telegram"
|
||||||
|
|
||||||
|
# download bazel
|
||||||
|
mkdir -p $HOME/bazel-dist
|
||||||
|
pushd $HOME/bazel-dist
|
||||||
|
curl -O -L https://github.com/bazelbuild/bazel/releases/download/3.7.0/bazel-3.7.0-darwin-x86_64
|
||||||
|
mv bazel-3.7.0* bazel
|
||||||
|
chmod +x bazel
|
||||||
|
./bazel --version
|
||||||
|
popd
|
||||||
|
|
||||||
|
cd $SOURCE_DIR
|
||||||
|
|
||||||
|
export APP_VERSION=$(cat versions.json | python3 -c 'import json,sys;obj=json.load(sys.stdin);print(obj["app"]);')
|
||||||
|
export COMMIT_COUNT=$(git rev-list --count HEAD)
|
||||||
|
export COMMIT_COUNT="$(($COMMIT_COUNT+2000))"
|
||||||
|
export BUILD_NUMBER="$COMMIT_COUNT"
|
||||||
|
echo "BUILD_NUMBER=$(echo $BUILD_NUMBER)" >> $GITHUB_ENV
|
||||||
|
echo "APP_VERSION=$(echo $APP_VERSION)" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
# prepare temporary keychain
|
||||||
|
export MY_KEYCHAIN="temp.keychain"
|
||||||
|
export MY_KEYCHAIN_PASSWORD="secret"
|
||||||
|
security create-keychain -p "$MY_KEYCHAIN_PASSWORD" "$MY_KEYCHAIN"
|
||||||
|
security list-keychains -d user -s "$MY_KEYCHAIN" $(security list-keychains -d user | sed s/\"//g)
|
||||||
|
security set-keychain-settings "$MY_KEYCHAIN"
|
||||||
|
security unlock-keychain -p "$MY_KEYCHAIN_PASSWORD" "$MY_KEYCHAIN"
|
||||||
|
|
||||||
|
# install fake certificates
|
||||||
|
export CERTS_PATH="build-system/fake-codesigning/certs/distribution"
|
||||||
|
for f in "$CERTS_PATH"/*.p12; do
|
||||||
|
security import "$f" -k "$MY_KEYCHAIN" -P "" -T /usr/bin/codesign -T /usr/bin/security
|
||||||
|
done
|
||||||
|
# fake certificates are self-signed, so we need to manually mark them as trusted (otherwise bazel will not pick them up)
|
||||||
|
for f in "$CERTS_PATH"/*.cer; do
|
||||||
|
sudo security add-trusted-cert -d -r trustRoot -p codeSign -k "$MY_KEYCHAIN" "$f"
|
||||||
|
done
|
||||||
|
security set-key-partition-list -S apple-tool:,apple: -k "$MY_KEYCHAIN_PASSWORD" "$MY_KEYCHAIN"
|
||||||
|
|
||||||
|
# use the official release configuration
|
||||||
|
rm -rf $HOME/telegram-configuration
|
||||||
|
mkdir -p $HOME/telegram-configuration
|
||||||
|
cp -R build-system/example-configuration/* $HOME/telegram-configuration/
|
||||||
|
|
||||||
|
# build the app
|
||||||
|
python3 build-system/Make/Make.py \
|
||||||
|
--bazel="$HOME/bazel-dist/bazel" \
|
||||||
|
--bazelUserRoot="$BAZEL_USER_ROOT" \
|
||||||
|
build \
|
||||||
|
--configurationPath="$HOME/telegram-configuration" \
|
||||||
|
--buildNumber=$BUILD_NUMBER \
|
||||||
|
--configuration=release_universal
|
||||||
|
|
||||||
|
# collect ipa
|
||||||
|
OUTPUT_PATH="build/artifacts"
|
||||||
|
rm -rf "$OUTPUT_PATH"
|
||||||
|
mkdir -p "$OUTPUT_PATH"
|
||||||
|
for f in bazel-out/applebin_ios-ios_arm*-opt-ST-*/bin/Telegram/Telegram.ipa; do
|
||||||
|
cp "$f" $OUTPUT_PATH/
|
||||||
|
done
|
||||||
|
|
||||||
|
# collect dsym
|
||||||
|
mkdir -p build/DSYMs
|
||||||
|
for f in bazel-out/applebin_ios-ios_arm*-opt-ST-*/bin/Telegram/*.dSYM; do
|
||||||
|
cp -R "$f" build/DSYMs/
|
||||||
|
done
|
||||||
|
zip -r "./$OUTPUT_PATH/Telegram.DSYMs.zip" build/DSYMs 1>/dev/null
|
||||||
|
|
||||||
|
- name: Create Release
|
||||||
|
id: create_release
|
||||||
|
uses: actions/create-release@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
tag_name: build-${{ env.BUILD_NUMBER }}
|
||||||
|
release_name: Telegram ${{ env.APP_VERSION }} (${{ env.BUILD_NUMBER }})
|
||||||
|
body: |
|
||||||
|
An unsigned build of Telegram for iOS ${{ env.APP_VERSION }} (${{ env.BUILD_NUMBER }})
|
||||||
|
draft: false
|
||||||
|
prerelease: false
|
||||||
|
|
||||||
|
- name: Upload Release IPA
|
||||||
|
id: upload-release-ipa
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
asset_path: /Users/telegram/telegram-ios/build/artifacts/Telegram.ipa
|
||||||
|
asset_name: Telegram.ipa
|
||||||
|
asset_content_type: application/zip
|
||||||
|
|
||||||
|
- name: Upload Release DSYM
|
||||||
|
id: upload-release-dsym
|
||||||
|
uses: actions/upload-release-asset@v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||||
|
asset_path: /Users/telegram/telegram-ios/build/artifacts/Telegram.DSYMs.zip
|
||||||
|
asset_name: Telegram.DSYMs.zip
|
||||||
|
asset_content_type: application/zip
|
||||||
@ -5959,6 +5959,7 @@ Sorry for the inconvenience.";
|
|||||||
"ChatImportActivity.ErrorInvalidChatType" = "Wrong type of chat for the messages you are trying to import.";
|
"ChatImportActivity.ErrorInvalidChatType" = "Wrong type of chat for the messages you are trying to import.";
|
||||||
"ChatImportActivity.ErrorUserBlocked" = "Unable to import messages due to privacy settings.";
|
"ChatImportActivity.ErrorUserBlocked" = "Unable to import messages due to privacy settings.";
|
||||||
"ChatImportActivity.ErrorGeneric" = "An error occurred.";
|
"ChatImportActivity.ErrorGeneric" = "An error occurred.";
|
||||||
|
"ChatImportActivity.ErrorLimitExceeded" = "Daily maximum reached,\nplease come back tomorrow.";
|
||||||
"ChatImportActivity.Success" = "Chat imported\nsuccessfully.";
|
"ChatImportActivity.Success" = "Chat imported\nsuccessfully.";
|
||||||
|
|
||||||
"VoiceOver.Chat.GoToOriginalMessage" = "Go to message";
|
"VoiceOver.Chat.GoToOriginalMessage" = "Go to message";
|
||||||
@ -5978,3 +5979,13 @@ Sorry for the inconvenience.";
|
|||||||
"VoiceOver.Chat.Profile" = "Profile";
|
"VoiceOver.Chat.Profile" = "Profile";
|
||||||
"VoiceOver.Chat.GroupInfo" = "Group Info";
|
"VoiceOver.Chat.GroupInfo" = "Group Info";
|
||||||
"VoiceOver.Chat.ChannelInfo" = "Channel Info";
|
"VoiceOver.Chat.ChannelInfo" = "Channel Info";
|
||||||
|
|
||||||
|
"Conversation.ForwardTooltip.Chat.One" = "Message forwarded to **%@**";
|
||||||
|
"Conversation.ForwardTooltip.Chat.Many" = "Messages forwarded to **%@**";
|
||||||
|
"Conversation.ForwardTooltip.TwoChats.One" = "Message forwarded to **%@** and **%@**";
|
||||||
|
"Conversation.ForwardTooltip.TwoChats.Many" = "Messages forwarded to **%@** and **%@**";
|
||||||
|
"Conversation.ForwardTooltip.ManyChats.One" = "Message forwarded to **%@** and %@ others";
|
||||||
|
"Conversation.ForwardTooltip.ManyChats.Many" = "Messages forwarded to **%@** and %@ others";
|
||||||
|
|
||||||
|
"Conversation.ForwardTooltip.SavedMessages.One" = "Message forwarded to **Saved Messages**";
|
||||||
|
"Conversation.ForwardTooltip.SavedMessages.Many" = "Messages forwarded to **Saved Messages**";
|
||||||
|
|||||||
@ -12,7 +12,7 @@ from ProjectGeneration import generate
|
|||||||
|
|
||||||
|
|
||||||
class BazelCommandLine:
|
class BazelCommandLine:
|
||||||
def __init__(self, bazel_path, bazel_x86_64_path, override_bazel_version, override_xcode_version):
|
def __init__(self, bazel_path, bazel_x86_64_path, override_bazel_version, override_xcode_version, bazel_user_root):
|
||||||
self.build_environment = BuildEnvironment(
|
self.build_environment = BuildEnvironment(
|
||||||
base_path=os.getcwd(),
|
base_path=os.getcwd(),
|
||||||
bazel_path=bazel_path,
|
bazel_path=bazel_path,
|
||||||
@ -20,6 +20,7 @@ class BazelCommandLine:
|
|||||||
override_bazel_version=override_bazel_version,
|
override_bazel_version=override_bazel_version,
|
||||||
override_xcode_version=override_xcode_version
|
override_xcode_version=override_xcode_version
|
||||||
)
|
)
|
||||||
|
self.bazel_user_root = bazel_user_root
|
||||||
self.remote_cache = None
|
self.remote_cache = None
|
||||||
self.cache_dir = None
|
self.cache_dir = None
|
||||||
self.additional_args = None
|
self.additional_args = None
|
||||||
@ -173,9 +174,18 @@ class BazelCommandLine:
|
|||||||
else:
|
else:
|
||||||
raise Exception('Unknown configuration {}'.format(configuration))
|
raise Exception('Unknown configuration {}'.format(configuration))
|
||||||
|
|
||||||
|
def get_startup_bazel_arguments(self):
|
||||||
|
combined_arguments = []
|
||||||
|
if self.bazel_user_root is not None:
|
||||||
|
combined_arguments += ['--output_user_root={}'.format(self.bazel_user_root)]
|
||||||
|
return combined_arguments
|
||||||
|
|
||||||
def invoke_clean(self):
|
def invoke_clean(self):
|
||||||
combined_arguments = [
|
combined_arguments = [
|
||||||
self.build_environment.bazel_path,
|
self.build_environment.bazel_path
|
||||||
|
]
|
||||||
|
combined_arguments += self.get_startup_bazel_arguments()
|
||||||
|
combined_arguments += [
|
||||||
'clean',
|
'clean',
|
||||||
'--expunge'
|
'--expunge'
|
||||||
]
|
]
|
||||||
@ -209,7 +219,10 @@ class BazelCommandLine:
|
|||||||
|
|
||||||
def invoke_build(self):
|
def invoke_build(self):
|
||||||
combined_arguments = [
|
combined_arguments = [
|
||||||
self.build_environment.bazel_path,
|
self.build_environment.bazel_path
|
||||||
|
]
|
||||||
|
combined_arguments += self.get_startup_bazel_arguments()
|
||||||
|
combined_arguments += [
|
||||||
'build',
|
'build',
|
||||||
'Telegram/Telegram'
|
'Telegram/Telegram'
|
||||||
]
|
]
|
||||||
@ -247,7 +260,8 @@ def clean(arguments):
|
|||||||
bazel_path=arguments.bazel,
|
bazel_path=arguments.bazel,
|
||||||
bazel_x86_64_path=None,
|
bazel_x86_64_path=None,
|
||||||
override_bazel_version=arguments.overrideBazelVersion,
|
override_bazel_version=arguments.overrideBazelVersion,
|
||||||
override_xcode_version=arguments.overrideXcodeVersion
|
override_xcode_version=arguments.overrideXcodeVersion,
|
||||||
|
bazel_user_root=arguments.bazelUserRoot
|
||||||
)
|
)
|
||||||
|
|
||||||
bazel_command_line.invoke_clean()
|
bazel_command_line.invoke_clean()
|
||||||
@ -292,7 +306,8 @@ def generate_project(arguments):
|
|||||||
bazel_path=arguments.bazel,
|
bazel_path=arguments.bazel,
|
||||||
bazel_x86_64_path=bazel_x86_64_path,
|
bazel_x86_64_path=bazel_x86_64_path,
|
||||||
override_bazel_version=arguments.overrideBazelVersion,
|
override_bazel_version=arguments.overrideBazelVersion,
|
||||||
override_xcode_version=arguments.overrideXcodeVersion
|
override_xcode_version=arguments.overrideXcodeVersion,
|
||||||
|
bazel_user_root=arguments.bazelUserRoot
|
||||||
)
|
)
|
||||||
|
|
||||||
if arguments.cacheDir is not None:
|
if arguments.cacheDir is not None:
|
||||||
@ -326,7 +341,8 @@ def build(arguments):
|
|||||||
bazel_path=arguments.bazel,
|
bazel_path=arguments.bazel,
|
||||||
bazel_x86_64_path=None,
|
bazel_x86_64_path=None,
|
||||||
override_bazel_version=arguments.overrideBazelVersion,
|
override_bazel_version=arguments.overrideBazelVersion,
|
||||||
override_xcode_version=arguments.overrideXcodeVersion
|
override_xcode_version=arguments.overrideXcodeVersion,
|
||||||
|
bazel_user_root=arguments.bazelUserRoot
|
||||||
)
|
)
|
||||||
|
|
||||||
if arguments.cacheDir is not None:
|
if arguments.cacheDir is not None:
|
||||||
@ -383,6 +399,13 @@ if __name__ == '__main__':
|
|||||||
metavar='path'
|
metavar='path'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'--bazelUserRoot',
|
||||||
|
required=False,
|
||||||
|
help='Use custom bazel user root (useful when reproducing a build)',
|
||||||
|
metavar='path'
|
||||||
|
)
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--overrideBazelVersion',
|
'--overrideBazelVersion',
|
||||||
action='store_true',
|
action='store_true',
|
||||||
|
|||||||
@ -57,6 +57,7 @@ private final class ImportManager {
|
|||||||
case chatAdminRequired
|
case chatAdminRequired
|
||||||
case invalidChatType
|
case invalidChatType
|
||||||
case userBlocked
|
case userBlocked
|
||||||
|
case limitExceeded
|
||||||
}
|
}
|
||||||
|
|
||||||
enum State {
|
enum State {
|
||||||
@ -108,6 +109,11 @@ private final class ImportManager {
|
|||||||
|
|
||||||
self.stateValue = .progress(totalBytes: self.totalBytes, totalUploadedBytes: 0, totalMediaBytes: self.totalMediaBytes, totalUploadedMediaBytes: 0)
|
self.stateValue = .progress(totalBytes: self.totalBytes, totalUploadedBytes: 0, totalMediaBytes: self.totalMediaBytes, totalUploadedMediaBytes: 0)
|
||||||
|
|
||||||
|
Logger.shared.log("ChatImportScreen", "Requesting import session for \(peerId), media count: \(entries.count) with pending entries:")
|
||||||
|
for entry in entries {
|
||||||
|
Logger.shared.log("ChatImportScreen", " \(entry.1)")
|
||||||
|
}
|
||||||
|
|
||||||
self.disposable.set((ChatHistoryImport.initSession(account: self.account, peerId: peerId, file: mainFile, mediaCount: Int32(entries.count))
|
self.disposable.set((ChatHistoryImport.initSession(account: self.account, peerId: peerId, file: mainFile, mediaCount: Int32(entries.count))
|
||||||
|> mapError { error -> ImportError in
|
|> mapError { error -> ImportError in
|
||||||
switch error {
|
switch error {
|
||||||
@ -119,6 +125,8 @@ private final class ImportManager {
|
|||||||
return .generic
|
return .generic
|
||||||
case .userBlocked:
|
case .userBlocked:
|
||||||
return .userBlocked
|
return .userBlocked
|
||||||
|
case .limitExceeded:
|
||||||
|
return .limitExceeded
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|> deliverOnMainQueue).start(next: { [weak self] session in
|
|> deliverOnMainQueue).start(next: { [weak self] session in
|
||||||
@ -188,28 +196,37 @@ private final class ImportManager {
|
|||||||
|
|
||||||
private func updateState() {
|
private func updateState() {
|
||||||
guard let session = self.session else {
|
guard let session = self.session else {
|
||||||
|
Logger.shared.log("ChatImportScreen", "updateState called with no session, ignoring")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if self.pendingEntries.isEmpty && self.activeEntries.isEmpty {
|
if self.pendingEntries.isEmpty && self.activeEntries.isEmpty {
|
||||||
|
Logger.shared.log("ChatImportScreen", "updateState called with no pending and no active entries, completing")
|
||||||
self.complete()
|
self.complete()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if case .error = self.stateValue {
|
if case .error = self.stateValue {
|
||||||
|
Logger.shared.log("ChatImportScreen", "updateState called after error, ignoring")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard let archivePath = self.archivePath else {
|
guard let archivePath = self.archivePath else {
|
||||||
|
Logger.shared.log("ChatImportScreen", "updateState called with empty arhivePath, ignoring")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
while true {
|
while true {
|
||||||
if self.activeEntries.count >= 3 {
|
if self.activeEntries.count >= 3 {
|
||||||
|
Logger.shared.log("ChatImportScreen", "updateState concurrent processing limit reached, stop searching")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if self.pendingEntries.isEmpty {
|
if self.pendingEntries.isEmpty {
|
||||||
|
Logger.shared.log("ChatImportScreen", "updateState no more pending entries, stop searching")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
let entry = self.pendingEntries.removeFirst()
|
let entry = self.pendingEntries.removeFirst()
|
||||||
|
|
||||||
|
Logger.shared.log("ChatImportScreen", "updateState take pending entry \(entry.1)")
|
||||||
|
|
||||||
let unpackedFile = Signal<TempBoxFile, ImportError> { subscriber in
|
let unpackedFile = Signal<TempBoxFile, ImportError> { subscriber in
|
||||||
let tempFile = TempBox.shared.tempFile(fileName: entry.0.path)
|
let tempFile = TempBox.shared.tempFile(fileName: entry.0.path)
|
||||||
Logger.shared.log("ChatImportScreen", "Extracting \(entry.0.path) to \(tempFile.path)...")
|
Logger.shared.log("ChatImportScreen", "Extracting \(entry.0.path) to \(tempFile.path)...")
|
||||||
@ -266,6 +283,7 @@ private final class ImportManager {
|
|||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
Logger.shared.log("ChatImportScreen", "updateState entry \(entry.1) has completed upload")
|
||||||
strongSelf.activeEntries.removeValue(forKey: entry.0.path)
|
strongSelf.activeEntries.removeValue(forKey: entry.0.path)
|
||||||
strongSelf.updateState()
|
strongSelf.updateState()
|
||||||
}))
|
}))
|
||||||
@ -533,6 +551,8 @@ public final class ChatImportActivityScreen: ViewController {
|
|||||||
errorText = self.presentationData.strings.ChatImportActivity_ErrorGeneric
|
errorText = self.presentationData.strings.ChatImportActivity_ErrorGeneric
|
||||||
case .userBlocked:
|
case .userBlocked:
|
||||||
errorText = self.presentationData.strings.ChatImportActivity_ErrorUserBlocked
|
errorText = self.presentationData.strings.ChatImportActivity_ErrorUserBlocked
|
||||||
|
case .limitExceeded:
|
||||||
|
errorText = self.presentationData.strings.ChatImportActivity_ErrorLimitExceeded
|
||||||
}
|
}
|
||||||
self.statusText.attributedText = NSAttributedString(string: errorText, font: Font.regular(17.0), textColor: self.presentationData.theme.list.itemDestructiveColor)
|
self.statusText.attributedText = NSAttributedString(string: errorText, font: Font.regular(17.0), textColor: self.presentationData.theme.list.itemDestructiveColor)
|
||||||
case .done:
|
case .done:
|
||||||
|
|||||||
@ -29,6 +29,7 @@ import GalleryData
|
|||||||
import InstantPageUI
|
import InstantPageUI
|
||||||
import ChatInterfaceState
|
import ChatInterfaceState
|
||||||
import ShareController
|
import ShareController
|
||||||
|
import UndoUI
|
||||||
|
|
||||||
private enum ChatListTokenId: Int32 {
|
private enum ChatListTokenId: Int32 {
|
||||||
case filter
|
case filter
|
||||||
@ -899,6 +900,9 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo
|
|||||||
let peerId = peer.id
|
let peerId = peer.id
|
||||||
if let strongSelf = self, let _ = peerSelectionController {
|
if let strongSelf = self, let _ = peerSelectionController {
|
||||||
if peerId == strongSelf.context.account.peerId {
|
if peerId == strongSelf.context.account.peerId {
|
||||||
|
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
(strongSelf.navigationController?.topViewController as? ViewController)?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: true, text: messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_SavedMessages_One : presentationData.strings.Conversation_ForwardTooltip_SavedMessages_Many), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
|
||||||
|
|
||||||
let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: messageIds.map { id -> EnqueueMessage in
|
let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: messageIds.map { id -> EnqueueMessage in
|
||||||
return .forward(source: id, grouping: .auto, attributes: [])
|
return .forward(source: id, grouping: .auto, attributes: [])
|
||||||
})
|
})
|
||||||
|
|||||||
@ -7,6 +7,22 @@ struct KeyboardSurface {
|
|||||||
let host: UIView
|
let host: UIView
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public extension UIResponder {
|
||||||
|
private struct Static {
|
||||||
|
static weak var responder: UIResponder?
|
||||||
|
}
|
||||||
|
|
||||||
|
static func currentFirst() -> UIResponder? {
|
||||||
|
Static.responder = nil
|
||||||
|
UIApplication.shared.sendAction(#selector(UIResponder._trap), to: nil, from: nil, for: nil)
|
||||||
|
return Static.responder
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func _trap() {
|
||||||
|
Static.responder = self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func getFirstResponder(_ view: UIView) -> UIView? {
|
private func getFirstResponder(_ view: UIView) -> UIView? {
|
||||||
if view.isFirstResponder {
|
if view.isFirstResponder {
|
||||||
return view
|
return view
|
||||||
|
|||||||
@ -219,10 +219,6 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
self.displayNode = Node(context: self.context, peerId: self.peerId, controller: self)
|
self.displayNode = Node(context: self.context, peerId: self.peerId, controller: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func loadView() {
|
|
||||||
super.loadView()
|
|
||||||
}
|
|
||||||
|
|
||||||
private var didAppearOnce: Bool = false
|
private var didAppearOnce: Bool = false
|
||||||
private var isDismissed: Bool = false
|
private var isDismissed: Bool = false
|
||||||
public override func viewDidAppear(_ animated: Bool) {
|
public override func viewDidAppear(_ animated: Bool) {
|
||||||
|
|||||||
@ -12,7 +12,7 @@ private final class WrappedLegacyReachability: NSObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static let thread: Thread = {
|
private static let thread: Thread = {
|
||||||
let thread = Thread(target: Reachability.self, selector: #selector(WrappedLegacyReachability.threadImpl), object: nil)
|
let thread = Thread(target: WrappedLegacyReachability.self, selector: #selector(WrappedLegacyReachability.threadImpl), object: nil)
|
||||||
thread.start()
|
thread.start()
|
||||||
return thread
|
return thread
|
||||||
}()
|
}()
|
||||||
@ -84,7 +84,7 @@ private final class WrappedLegacyReachability: NSObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(iOSApplicationExtension 12.0, iOS 12.0, *)
|
@available(iOSApplicationExtension 12.0, iOS 12.0, OSX 10.14, *)
|
||||||
private final class PathMonitor {
|
private final class PathMonitor {
|
||||||
private let queue: Queue
|
private let queue: Queue
|
||||||
private let monitor: NWPathMonitor
|
private let monitor: NWPathMonitor
|
||||||
@ -133,7 +133,7 @@ private final class PathMonitor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(iOSApplicationExtension 12.0, iOS 12.0, *)
|
@available(iOSApplicationExtension 12.0, iOS 12.0, OSX 10.14, *)
|
||||||
private final class SharedPathMonitor {
|
private final class SharedPathMonitor {
|
||||||
static let queue = Queue()
|
static let queue = Queue()
|
||||||
static let impl = QueueLocalObject<PathMonitor>(queue: queue, generate: {
|
static let impl = QueueLocalObject<PathMonitor>(queue: queue, generate: {
|
||||||
@ -149,7 +149,7 @@ public enum Reachability {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static var networkType: Signal<NetworkType, NoError> {
|
public static var networkType: Signal<NetworkType, NoError> {
|
||||||
if #available(iOSApplicationExtension 12.0, iOS 12.0, *) {
|
if #available(iOSApplicationExtension 12.0, iOS 12.0, OSX 10.14, *) {
|
||||||
return Signal { subscriber in
|
return Signal { subscriber in
|
||||||
let disposable = MetaDisposable()
|
let disposable = MetaDisposable()
|
||||||
|
|
||||||
|
|||||||
@ -305,6 +305,13 @@ public final class ShareController: ViewController {
|
|||||||
private var defaultAction: ShareControllerAction?
|
private var defaultAction: ShareControllerAction?
|
||||||
|
|
||||||
public var dismissed: ((Bool) -> Void)?
|
public var dismissed: ((Bool) -> Void)?
|
||||||
|
public var completed: (([PeerId]) -> Void)? {
|
||||||
|
didSet {
|
||||||
|
if self.isNodeLoaded {
|
||||||
|
self.controllerNode.completed = completed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public convenience init(context: AccountContext, subject: ShareControllerSubject, presetText: String? = nil, preferredAction: ShareControllerPreferredAction = .default, showInChat: ((Message) -> Void)? = nil, openStats: (() -> Void)? = nil, fromForeignApp: Bool = false, shares: Int? = nil, externalShare: Bool = true, immediateExternalShare: Bool = false, switchableAccounts: [AccountWithInfo] = [], immediatePeerId: PeerId? = nil, forcedTheme: PresentationTheme? = nil, forcedActionTitle: String? = nil) {
|
public convenience init(context: AccountContext, subject: ShareControllerSubject, presetText: String? = nil, preferredAction: ShareControllerPreferredAction = .default, showInChat: ((Message) -> Void)? = nil, openStats: (() -> Void)? = nil, fromForeignApp: Bool = false, shares: Int? = nil, externalShare: Bool = true, immediateExternalShare: Bool = false, switchableAccounts: [AccountWithInfo] = [], immediatePeerId: PeerId? = nil, forcedTheme: PresentationTheme? = nil, forcedActionTitle: String? = nil) {
|
||||||
self.init(sharedContext: context.sharedContext, currentContext: context, subject: subject, presetText: presetText, preferredAction: preferredAction, showInChat: showInChat, openStats: openStats, fromForeignApp: fromForeignApp, shares: shares, externalShare: externalShare, immediateExternalShare: immediateExternalShare, switchableAccounts: switchableAccounts, immediatePeerId: immediatePeerId, forcedTheme: forcedTheme, forcedActionTitle: forcedActionTitle)
|
self.init(sharedContext: context.sharedContext, currentContext: context, subject: subject, presetText: presetText, preferredAction: preferredAction, showInChat: showInChat, openStats: openStats, fromForeignApp: fromForeignApp, shares: shares, externalShare: externalShare, immediateExternalShare: immediateExternalShare, switchableAccounts: switchableAccounts, immediatePeerId: immediatePeerId, forcedTheme: forcedTheme, forcedActionTitle: forcedActionTitle)
|
||||||
@ -451,6 +458,7 @@ public final class ShareController: ViewController {
|
|||||||
}
|
}
|
||||||
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: title, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: title, text: text, actions: [TextAlertAction(type: .defaultAction, title: strongSelf.presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||||
}, externalShare: self.externalShare, immediateExternalShare: self.immediateExternalShare, immediatePeerId: self.immediatePeerId, shares: self.shares, fromForeignApp: self.fromForeignApp, forcedTheme: self.forcedTheme)
|
}, externalShare: self.externalShare, immediateExternalShare: self.immediateExternalShare, immediatePeerId: self.immediatePeerId, shares: self.shares, fromForeignApp: self.fromForeignApp, forcedTheme: self.forcedTheme)
|
||||||
|
self.controllerNode.completed = completed
|
||||||
self.controllerNode.dismiss = { [weak self] shared in
|
self.controllerNode.dismiss = { [weak self] shared in
|
||||||
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||||
self?.dismissed?(shared)
|
self?.dismissed?(shared)
|
||||||
@ -466,6 +474,7 @@ public final class ShareController: ViewController {
|
|||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return .complete()
|
return .complete()
|
||||||
}
|
}
|
||||||
|
|
||||||
var shareSignals: [Signal<[MessageId?], NoError>] = []
|
var shareSignals: [Signal<[MessageId?], NoError>] = []
|
||||||
switch strongSelf.subject {
|
switch strongSelf.subject {
|
||||||
case let .url(url):
|
case let .url(url):
|
||||||
|
|||||||
@ -65,6 +65,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
var shareExternal: (() -> Signal<ShareExternalState, NoError>)?
|
var shareExternal: (() -> Signal<ShareExternalState, NoError>)?
|
||||||
var switchToAnotherAccount: (() -> Void)?
|
var switchToAnotherAccount: (() -> Void)?
|
||||||
var openStats: (() -> Void)?
|
var openStats: (() -> Void)?
|
||||||
|
var completed: (([PeerId]) -> Void)?
|
||||||
|
|
||||||
let ready = Promise<Bool>()
|
let ready = Promise<Bool>()
|
||||||
private var didSetReady = false
|
private var didSetReady = false
|
||||||
@ -585,6 +586,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
Queue.mainQueue().after(delay, {
|
Queue.mainQueue().after(delay, {
|
||||||
self?.animateOut(shared: true, completion: {
|
self?.animateOut(shared: true, completion: {
|
||||||
self?.dismiss?(true)
|
self?.dismiss?(true)
|
||||||
|
self?.completed?(peerIds)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -977,6 +977,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
var result: [(PeerId, UInt32, Float, Bool)] = []
|
var result: [(PeerId, UInt32, Float, Bool)] = []
|
||||||
var myLevel: Float = 0.0
|
var myLevel: Float = 0.0
|
||||||
var myLevelHasVoice: Bool = false
|
var myLevelHasVoice: Bool = false
|
||||||
|
var missingSsrcs = Set<UInt32>()
|
||||||
for (ssrcKey, level, hasVoice) in levels {
|
for (ssrcKey, level, hasVoice) in levels {
|
||||||
var peerId: PeerId?
|
var peerId: PeerId?
|
||||||
let ssrcValue: UInt32
|
let ssrcValue: UInt32
|
||||||
@ -996,6 +997,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
result.append((peerId, ssrcValue, level, hasVoice))
|
result.append((peerId, ssrcValue, level, hasVoice))
|
||||||
|
} else if ssrcValue != 0 {
|
||||||
|
missingSsrcs.insert(ssrcValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1004,6 +1007,10 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
let mappedLevel = myLevel * 1.5
|
let mappedLevel = myLevel * 1.5
|
||||||
strongSelf.myAudioLevelPipe.putNext(mappedLevel)
|
strongSelf.myAudioLevelPipe.putNext(mappedLevel)
|
||||||
strongSelf.processMyAudioLevel(level: mappedLevel, hasVoice: myLevelHasVoice)
|
strongSelf.processMyAudioLevel(level: mappedLevel, hasVoice: myLevelHasVoice)
|
||||||
|
|
||||||
|
if !missingSsrcs.isEmpty {
|
||||||
|
strongSelf.participantsContext?.ensureHaveParticipants(ssrcs: missingSsrcs)
|
||||||
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1490,7 +1490,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if case let .known(value) = offset, value < 100.0 {
|
if case let .known(value) = offset, value < 200.0 {
|
||||||
if let loadMoreToken = strongSelf.currentCallMembers?.1 {
|
if let loadMoreToken = strongSelf.currentCallMembers?.1 {
|
||||||
strongSelf.currentLoadToken = loadMoreToken
|
strongSelf.currentLoadToken = loadMoreToken
|
||||||
strongSelf.call.loadMoreMembers(token: loadMoreToken)
|
strongSelf.call.loadMoreMembers(token: loadMoreToken)
|
||||||
|
|||||||
@ -167,7 +167,10 @@ func mergeGroupOrChannel(lhs: Peer?, rhs: Api.Chat) -> Peer? {
|
|||||||
case .broadcast:
|
case .broadcast:
|
||||||
break
|
break
|
||||||
case .group:
|
case .group:
|
||||||
let infoFlags = TelegramChannelGroupFlags()
|
var infoFlags = TelegramChannelGroupFlags()
|
||||||
|
if (flags & Int32(1 << 22)) != 0 {
|
||||||
|
infoFlags.insert(.slowModeEnabled)
|
||||||
|
}
|
||||||
info = .group(TelegramChannelGroupInfo(flags: infoFlags))
|
info = .group(TelegramChannelGroupInfo(flags: infoFlags))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -16,6 +16,7 @@ public enum ChatHistoryImport {
|
|||||||
case chatAdminRequired
|
case chatAdminRequired
|
||||||
case invalidChatType
|
case invalidChatType
|
||||||
case userBlocked
|
case userBlocked
|
||||||
|
case limitExceeded
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ParsedInfo {
|
public enum ParsedInfo {
|
||||||
@ -64,16 +65,17 @@ public enum ChatHistoryImport {
|
|||||||
guard let inputPeer = inputPeer else {
|
guard let inputPeer = inputPeer else {
|
||||||
return .fail(.generic)
|
return .fail(.generic)
|
||||||
}
|
}
|
||||||
return account.network.request(Api.functions.messages.initHistoryImport(peer: inputPeer, file: inputFile, mediaCount: mediaCount))
|
return account.network.request(Api.functions.messages.initHistoryImport(peer: inputPeer, file: inputFile, mediaCount: mediaCount), automaticFloodWait: false)
|
||||||
|> mapError { error -> InitImportError in
|
|> mapError { error -> InitImportError in
|
||||||
switch error.errorDescription {
|
if error.errorDescription == "CHAT_ADMIN_REQUIRED" {
|
||||||
case "CHAT_ADMIN_REQUIRED":
|
|
||||||
return .chatAdminRequired
|
return .chatAdminRequired
|
||||||
case "IMPORT_PEER_TYPE_INVALID":
|
} else if error.errorDescription == "IMPORT_PEER_TYPE_INVALID" {
|
||||||
return .invalidChatType
|
return .invalidChatType
|
||||||
case "USER_IS_BLOCKED":
|
} else if error.errorDescription == "USER_IS_BLOCKED" {
|
||||||
return .userBlocked
|
return .userBlocked
|
||||||
default:
|
} else if error.errorDescription == "FLOOD_WAIT" {
|
||||||
|
return .limitExceeded
|
||||||
|
} else {
|
||||||
return .generic
|
return .generic
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -107,7 +109,10 @@ public enum ChatHistoryImport {
|
|||||||
|
|
||||||
public static func uploadMedia(account: Account, session: Session, file: TempBoxFile, fileName: String, mimeType: String, type: MediaType) -> Signal<Float, UploadMediaError> {
|
public static func uploadMedia(account: Account, session: Session, file: TempBoxFile, fileName: String, mimeType: String, type: MediaType) -> Signal<Float, UploadMediaError> {
|
||||||
var forceNoBigParts = true
|
var forceNoBigParts = true
|
||||||
if let size = fileSize(file.path), size >= 30 * 1024 * 1024 {
|
guard let size = fileSize(file.path), size != 0 else {
|
||||||
|
return .single(1.0)
|
||||||
|
}
|
||||||
|
if size >= 30 * 1024 * 1024 {
|
||||||
forceNoBigParts = false
|
forceNoBigParts = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -989,7 +989,7 @@ public final class GroupCallParticipantsContext {
|
|||||||
self.ensureHaveParticipants(ssrcs: Set(ids.map { $0.1 }))
|
self.ensureHaveParticipants(ssrcs: Set(ids.map { $0.1 }))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func ensureHaveParticipants(ssrcs: Set<UInt32>) {
|
public func ensureHaveParticipants(ssrcs: Set<UInt32>) {
|
||||||
var missingSsrcs = Set<UInt32>()
|
var missingSsrcs = Set<UInt32>()
|
||||||
|
|
||||||
var existingSsrcs = Set<UInt32>()
|
var existingSsrcs = Set<UInt32>()
|
||||||
|
|||||||
@ -6,6 +6,66 @@ import MtProtoKit
|
|||||||
|
|
||||||
import SyncCore
|
import SyncCore
|
||||||
|
|
||||||
|
|
||||||
|
public func ensuredExistingPeerExportedInvitation(account: Account, peerId: PeerId, revokeExisted: Bool = false) -> Signal<ExportedInvitation?, NoError> {
|
||||||
|
return account.postbox.transaction { transaction -> Signal<ExportedInvitation?, NoError> in
|
||||||
|
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
||||||
|
if let _ = peer as? TelegramChannel {
|
||||||
|
if let cachedData = transaction.getPeerCachedData(peerId: peerId) as? CachedChannelData, cachedData.exportedInvitation != nil && !revokeExisted {
|
||||||
|
return .single(cachedData.exportedInvitation)
|
||||||
|
} else {
|
||||||
|
return account.network.request(Api.functions.messages.exportChatInvite(peer: inputPeer))
|
||||||
|
|> retryRequest
|
||||||
|
|> mapToSignal { result -> Signal<ExportedInvitation?, NoError> in
|
||||||
|
return account.postbox.transaction { transaction -> ExportedInvitation? in
|
||||||
|
if let invitation = ExportedInvitation(apiExportedInvite: result) {
|
||||||
|
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
|
||||||
|
if let current = current as? CachedChannelData {
|
||||||
|
return current.withUpdatedExportedInvitation(invitation)
|
||||||
|
} else {
|
||||||
|
return CachedChannelData().withUpdatedExportedInvitation(invitation)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return invitation
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let _ = peer as? TelegramGroup {
|
||||||
|
if let cachedData = transaction.getPeerCachedData(peerId: peerId) as? CachedGroupData, cachedData.exportedInvitation != nil && !revokeExisted {
|
||||||
|
return .single(cachedData.exportedInvitation)
|
||||||
|
} else {
|
||||||
|
return account.network.request(Api.functions.messages.exportChatInvite(peer: inputPeer))
|
||||||
|
|> retryRequest
|
||||||
|
|> mapToSignal { result -> Signal<ExportedInvitation?, NoError> in
|
||||||
|
return account.postbox.transaction { transaction -> ExportedInvitation? in
|
||||||
|
if let invitation = ExportedInvitation(apiExportedInvite: result) {
|
||||||
|
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
|
||||||
|
if let current = current as? CachedGroupData {
|
||||||
|
return current.withUpdatedExportedInvitation(invitation)
|
||||||
|
} else {
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return invitation
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
|
} |> switchToLatest
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public func revokePersistentPeerExportedInvitation(account: Account, peerId: PeerId) -> Signal<ExportedInvitation?, NoError> {
|
public func revokePersistentPeerExportedInvitation(account: Account, peerId: PeerId) -> Signal<ExportedInvitation?, NoError> {
|
||||||
return account.postbox.transaction { transaction -> Signal<ExportedInvitation?, NoError> in
|
return account.postbox.transaction { transaction -> Signal<ExportedInvitation?, NoError> in
|
||||||
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
if let peer = transaction.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
||||||
|
|||||||
@ -84,7 +84,7 @@ public extension TelegramChannel {
|
|||||||
if let defaultBannedRights = self.defaultBannedRights, defaultBannedRights.flags.contains(.banAddMembers) {
|
if let defaultBannedRights = self.defaultBannedRights, defaultBannedRights.flags.contains(.banAddMembers) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return false
|
return true
|
||||||
}
|
}
|
||||||
case .editAllMessages:
|
case .editAllMessages:
|
||||||
if let adminRights = self.adminRights, adminRights.flags.contains(.canEditMessages) {
|
if let adminRights = self.adminRights, adminRights.flags.contains(.canEditMessages) {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -1413,11 +1413,51 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}, openMessageShareMenu: { [weak self] id in
|
}, openMessageShareMenu: { [weak self] id in
|
||||||
if let strongSelf = self, let messages = strongSelf.chatDisplayNode.historyNode.messageGroupInCurrentHistoryView(id), let _ = messages.first {
|
if let strongSelf = self, let messages = strongSelf.chatDisplayNode.historyNode.messageGroupInCurrentHistoryView(id), let _ = messages.first {
|
||||||
let shareController = ShareController(context: strongSelf.context, subject: .messages(messages))
|
let shareController = ShareController(context: strongSelf.context, subject: .messages(messages))
|
||||||
shareController.dismissed = { shared in
|
shareController.dismissed = { [weak self] shared in
|
||||||
if shared {
|
if shared {
|
||||||
self?.commitPurposefulAction()
|
self?.commitPurposefulAction()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
shareController.completed = { [weak self] peerIds in
|
||||||
|
if let strongSelf = self {
|
||||||
|
let _ = (strongSelf.context.account.postbox.transaction { transaction -> [Peer] in
|
||||||
|
var peers: [Peer] = []
|
||||||
|
for peerId in peerIds {
|
||||||
|
if let peer = transaction.getPeer(peerId) {
|
||||||
|
peers.append(peer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return peers
|
||||||
|
} |> deliverOnMainQueue).start(next: { [weak self] peers in
|
||||||
|
if let strongSelf = self {
|
||||||
|
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
|
let text: String
|
||||||
|
var savedMessages = false
|
||||||
|
if peerIds.count == 1, let peerId = peerIds.first, peerId == strongSelf.context.account.peerId {
|
||||||
|
text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_SavedMessages_One : presentationData.strings.Conversation_ForwardTooltip_SavedMessages_Many
|
||||||
|
savedMessages = true
|
||||||
|
} else {
|
||||||
|
if peers.count == 1, let peer = peers.first {
|
||||||
|
let peerName = peer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||||
|
text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_Chat_One(peerName).0 : presentationData.strings.Conversation_ForwardTooltip_Chat_Many(peerName).0
|
||||||
|
} else if peers.count == 2, let firstPeer = peers.first, let secondPeer = peers.last {
|
||||||
|
let firstPeerName = firstPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : firstPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||||
|
let secondPeerName = secondPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : secondPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||||
|
text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_TwoChats_One(firstPeerName, secondPeerName).0 : presentationData.strings.Conversation_ForwardTooltip_TwoChats_Many(firstPeerName, secondPeerName).0
|
||||||
|
} else if let peer = peers.first {
|
||||||
|
let peerName = peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||||
|
text = messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_ManyChats_One(peerName, "\(peers.count - 1)").0 : presentationData.strings.Conversation_ForwardTooltip_ManyChats_Many(peerName, "\(peers.count - 1)").0
|
||||||
|
} else {
|
||||||
|
text = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: savedMessages, text: text), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
strongSelf.chatDisplayNode.dismissInput()
|
strongSelf.chatDisplayNode.dismissInput()
|
||||||
strongSelf.present(shareController, in: .window(.root), blockInteraction: true)
|
strongSelf.present(shareController, in: .window(.root), blockInteraction: true)
|
||||||
}
|
}
|
||||||
@ -10041,6 +10081,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
strongSelf.searchResultsController = nil
|
strongSelf.searchResultsController = nil
|
||||||
strongController.dismiss()
|
strongController.dismiss()
|
||||||
} else if peerId == strongSelf.context.account.peerId {
|
} else if peerId == strongSelf.context.account.peerId {
|
||||||
|
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: true, text: messages.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_SavedMessages_One : presentationData.strings.Conversation_ForwardTooltip_SavedMessages_Many), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
|
||||||
|
|
||||||
let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: messages.map { message -> EnqueueMessage in
|
let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: messages.map { message -> EnqueueMessage in
|
||||||
return .forward(source: message.id, grouping: .auto, attributes: [])
|
return .forward(source: message.id, grouping: .auto, attributes: [])
|
||||||
})
|
})
|
||||||
@ -11262,7 +11305,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
} else {
|
} else if UIResponder.currentFirst() == nil {
|
||||||
inputShortcuts = [
|
inputShortcuts = [
|
||||||
KeyShortcut(title: strings.KeyCommand_FocusOnInputField, input: "\r", action: { [weak self] in
|
KeyShortcut(title: strings.KeyCommand_FocusOnInputField, input: "\r", action: { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
@ -11316,6 +11359,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
|
} else {
|
||||||
|
inputShortcuts = []
|
||||||
}
|
}
|
||||||
|
|
||||||
var canEdit = false
|
var canEdit = false
|
||||||
|
|||||||
@ -54,6 +54,7 @@ import GalleryData
|
|||||||
import ChatInterfaceState
|
import ChatInterfaceState
|
||||||
import TelegramVoip
|
import TelegramVoip
|
||||||
import InviteLinksUI
|
import InviteLinksUI
|
||||||
|
import UndoUI
|
||||||
|
|
||||||
protocol PeerInfoScreenItem: class {
|
protocol PeerInfoScreenItem: class {
|
||||||
var id: AnyHashable { get }
|
var id: AnyHashable { get }
|
||||||
@ -4689,6 +4690,9 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
|||||||
|
|
||||||
if let strongSelf = self, let _ = peerSelectionController {
|
if let strongSelf = self, let _ = peerSelectionController {
|
||||||
if peerId == strongSelf.context.account.peerId {
|
if peerId == strongSelf.context.account.peerId {
|
||||||
|
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
strongSelf.controller?.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: true, text: messageIds.count == 1 ? presentationData.strings.Conversation_ForwardTooltip_SavedMessages_One : presentationData.strings.Conversation_ForwardTooltip_SavedMessages_Many), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
|
||||||
|
|
||||||
strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone)
|
strongSelf.headerNode.navigationButtonContainer.performAction?(.selectionDone)
|
||||||
|
|
||||||
let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: messageIds.map { id -> EnqueueMessage in
|
let _ = (enqueueMessages(account: strongSelf.context.account, peerId: peerId, messages: messageIds.map { id -> EnqueueMessage in
|
||||||
|
|||||||
@ -73,6 +73,32 @@ public struct ShareRootControllerInitializationData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func extractTextFileHeader(path: String) -> String? {
|
||||||
|
guard let file = ManagedFile(queue: nil, path: path, mode: .read) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
guard let size = file.getSize() else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
let limit = 3000
|
||||||
|
|
||||||
|
var data = file.readData(count: min(size, limit))
|
||||||
|
let additionalCapacity = min(10, max(0, size - data.count))
|
||||||
|
|
||||||
|
for alignment in 0 ... additionalCapacity {
|
||||||
|
if alignment != 0 {
|
||||||
|
data.append(file.readData(count: 1))
|
||||||
|
}
|
||||||
|
if let text = String(data: data, encoding: .utf8) {
|
||||||
|
return text
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
public class ShareRootControllerImpl {
|
public class ShareRootControllerImpl {
|
||||||
private let initializationData: ShareRootControllerInitializationData
|
private let initializationData: ShareRootControllerInitializationData
|
||||||
private let getExtensionContext: () -> NSExtensionContext?
|
private let getExtensionContext: () -> NSExtensionContext?
|
||||||
@ -523,11 +549,11 @@ public class ShareRootControllerImpl {
|
|||||||
return try? NSRegularExpression(pattern: string)
|
return try? NSRegularExpression(pattern: string)
|
||||||
}
|
}
|
||||||
|
|
||||||
if let mainFileText = try? String(contentsOf: URL(fileURLWithPath: url.path)) {
|
if let mainFileTextHeader = extractTextFileHeader(path: url.path) {
|
||||||
let fullRange = NSRange(mainFileText.startIndex ..< mainFileText.endIndex, in: mainFileText)
|
let fullRange = NSRange(mainFileTextHeader.startIndex ..< mainFileTextHeader.endIndex, in: mainFileTextHeader)
|
||||||
var foundMatch = false
|
var foundMatch = false
|
||||||
for pattern in filePatterns {
|
for pattern in filePatterns {
|
||||||
if pattern.firstMatch(in: mainFileText, options: [], range: fullRange) != nil {
|
if pattern.firstMatch(in: mainFileTextHeader, options: [], range: fullRange) != nil {
|
||||||
foundMatch = true
|
foundMatch = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -550,14 +576,7 @@ public class ShareRootControllerImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let mainFile = mainFile, let mainFileText = try? String(contentsOf: URL(fileURLWithPath: mainFile.path)) {
|
if let mainFile = mainFile, let mainFileHeader = extractTextFileHeader(path :mainFile.path) {
|
||||||
let mainFileHeader: String
|
|
||||||
if mainFileText.count < 2000 {
|
|
||||||
mainFileHeader = mainFileText
|
|
||||||
} else {
|
|
||||||
mainFileHeader = String(mainFileText[mainFileText.startIndex ..< mainFileText.index(mainFileText.startIndex, offsetBy: 2000)])
|
|
||||||
}
|
|
||||||
|
|
||||||
final class TempController: ViewController {
|
final class TempController: ViewController {
|
||||||
override public var _presentedInModal: Bool {
|
override public var _presentedInModal: Bool {
|
||||||
get {
|
get {
|
||||||
|
|||||||
@ -128,14 +128,6 @@ public final class TelegramRootController: NavigationController {
|
|||||||
self.accountSettingsController = accountSettingsController
|
self.accountSettingsController = accountSettingsController
|
||||||
self.rootTabController = tabBarController
|
self.rootTabController = tabBarController
|
||||||
self.pushViewController(tabBarController, animated: false)
|
self.pushViewController(tabBarController, animated: false)
|
||||||
|
|
||||||
Queue.mainQueue().after(1.0) {
|
|
||||||
let datePicker = DatePickerNode(theme: DatePickerTheme(backgroundColor: .white, textColor: .black, secondaryTextColor: .gray, accentColor: .blue, disabledColor: .lightGray, selectionColor: .blue, selectedCurrentTextColor: .white, secondarySelectionColor: .cyan), strings: self.context.sharedContext.currentPresentationData.with { $0 }.strings)
|
|
||||||
|
|
||||||
let frame = CGRect(origin: CGPoint(x: 50.0, y: 100.0), size: CGSize(width: 300.0, height: 300.0))
|
|
||||||
datePicker.updateLayout(size: frame.size, transition: .immediate)
|
|
||||||
self.rootTabController?.displayNode.addSubnode(datePicker)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func updateRootControllers(showCallsTab: Bool) {
|
public func updateRootControllers(showCallsTab: Bool) {
|
||||||
|
|||||||
@ -174,7 +174,7 @@ final class YoutubeEmbedImplementation: WebEmbedImplementation {
|
|||||||
updateStatus(self.status)
|
updateStatus(self.status)
|
||||||
|
|
||||||
let html = String(format: htmlTemplate, paramsJson)
|
let html = String(format: htmlTemplate, paramsJson)
|
||||||
webView.loadHTMLString(html, baseURL: URL(string: "https://messenger.telegram.org"))
|
webView.loadHTMLString(html, baseURL: URL(string: "https://telegram.youtube.com"))
|
||||||
// webView.isUserInteractionEnabled = false
|
// webView.isUserInteractionEnabled = false
|
||||||
|
|
||||||
userContentController.addUserScript(WKUserScript(source: userScript, injectionTime: .atDocumentEnd, forMainFrameOnly: false))
|
userContentController.addUserScript(WKUserScript(source: userScript, injectionTime: .atDocumentEnd, forMainFrameOnly: false))
|
||||||
|
|||||||
@ -28,6 +28,7 @@ public enum UndoOverlayContent {
|
|||||||
case banned(text: String)
|
case banned(text: String)
|
||||||
case importedMessage(text: String)
|
case importedMessage(text: String)
|
||||||
case audioRate(slowdown: Bool, text: String)
|
case audioRate(slowdown: Bool, text: String)
|
||||||
|
case forward(savedMessages: Bool, text: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum UndoOverlayAction {
|
public enum UndoOverlayAction {
|
||||||
|
|||||||
@ -492,6 +492,21 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
self.textNode.attributedText = attributedText
|
self.textNode.attributedText = attributedText
|
||||||
self.textNode.maximumNumberOfLines = 2
|
self.textNode.maximumNumberOfLines = 2
|
||||||
|
|
||||||
|
displayUndo = false
|
||||||
|
self.originalRemainingSeconds = 3
|
||||||
|
case let .forward(savedMessages, text):
|
||||||
|
self.avatarNode = nil
|
||||||
|
self.iconNode = nil
|
||||||
|
self.iconCheckNode = nil
|
||||||
|
self.animationNode = AnimationNode(animation: savedMessages ? "anim_savedmessages" : "anim_forward", colors: [:], scale: 0.066)
|
||||||
|
self.animatedStickerNode = nil
|
||||||
|
|
||||||
|
let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white)
|
||||||
|
let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white)
|
||||||
|
let attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in return nil }), textAlignment: .natural)
|
||||||
|
self.textNode.attributedText = attributedText
|
||||||
|
self.textNode.maximumNumberOfLines = 2
|
||||||
|
|
||||||
displayUndo = false
|
displayUndo = false
|
||||||
self.originalRemainingSeconds = 3
|
self.originalRemainingSeconds = 3
|
||||||
}
|
}
|
||||||
@ -522,7 +537,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
|
|||||||
switch content {
|
switch content {
|
||||||
case .removedChat:
|
case .removedChat:
|
||||||
self.panelWrapperNode.addSubnode(self.timerTextNode)
|
self.panelWrapperNode.addSubnode(self.timerTextNode)
|
||||||
case .archivedChat, .hidArchive, .revealedArchive, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified, .chatAddedToFolder, .chatRemovedFromFolder, .messagesUnpinned, .setProximityAlert, .invitedToVoiceChat, .linkCopied, .banned, .importedMessage, .audioRate:
|
case .archivedChat, .hidArchive, .revealedArchive, .succeed, .emoji, .swipeToReply, .actionSucceeded, .stickersModified, .chatAddedToFolder, .chatRemovedFromFolder, .messagesUnpinned, .setProximityAlert, .invitedToVoiceChat, .linkCopied, .banned, .importedMessage, .audioRate, .forward:
|
||||||
break
|
break
|
||||||
case .dice:
|
case .dice:
|
||||||
self.panelWrapperNode.clipsToBounds = true
|
self.panelWrapperNode.clipsToBounds = true
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user