Merge branch 'master' into experiments/openh264
@ -1469,6 +1469,7 @@ swift_library(
|
||||
"//submodules/TelegramVoip:TelegramVoip",
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/BuildConfig:BuildConfig",
|
||||
"//submodules/WidgetItems:WidgetItems",
|
||||
],
|
||||
)
|
||||
|
||||
@ -1819,3 +1820,51 @@ ios_application(
|
||||
":Lib",
|
||||
],
|
||||
)
|
||||
|
||||
# Temporary targets used to simplify webrtc build tests
|
||||
|
||||
ios_application(
|
||||
name = "webrtc_build_test",
|
||||
bundle_id = "{telegram_bundle_id}".format(
|
||||
telegram_bundle_id = telegram_bundle_id,
|
||||
),
|
||||
families = ["iphone", "ipad"],
|
||||
minimum_os_version = "9.0",
|
||||
provisioning_profile = select({
|
||||
":disableProvisioningProfilesSetting": None,
|
||||
"//conditions:default": "@build_configuration//provisioning:Telegram.mobileprovision",
|
||||
}),
|
||||
entitlements = ":TelegramEntitlements.entitlements",
|
||||
infoplists = [
|
||||
":TelegramInfoPlist",
|
||||
":BuildNumberInfoPlist",
|
||||
":VersionInfoPlist",
|
||||
":UrlTypesInfoPlist",
|
||||
],
|
||||
deps = [
|
||||
"//third-party/webrtc:webrtc_lib",
|
||||
],
|
||||
)
|
||||
|
||||
ios_application(
|
||||
name = "libvpx_build_test",
|
||||
bundle_id = "{telegram_bundle_id}".format(
|
||||
telegram_bundle_id = telegram_bundle_id,
|
||||
),
|
||||
families = ["iphone", "ipad"],
|
||||
minimum_os_version = "9.0",
|
||||
provisioning_profile = select({
|
||||
":disableProvisioningProfilesSetting": None,
|
||||
"//conditions:default": "@build_configuration//provisioning:Telegram.mobileprovision",
|
||||
}),
|
||||
entitlements = ":TelegramEntitlements.entitlements",
|
||||
infoplists = [
|
||||
":TelegramInfoPlist",
|
||||
":BuildNumberInfoPlist",
|
||||
":VersionInfoPlist",
|
||||
":UrlTypesInfoPlist",
|
||||
],
|
||||
deps = [
|
||||
"//third-party/libvpx:vpx",
|
||||
],
|
||||
)
|
||||
|
@ -164,9 +164,14 @@ private func rootPathForBasePath(_ appGroupPath: String) -> String {
|
||||
guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
|
||||
return
|
||||
}
|
||||
|
||||
var orientation = CGImagePropertyOrientation.up
|
||||
if #available(iOS 11.0, *) {
|
||||
if let orientationAttachment = CMGetAttachment(sampleBuffer, key: RPVideoSampleOrientationKey as CFString, attachmentModeOut: nil) as? NSNumber {
|
||||
orientation = CGImagePropertyOrientation(rawValue: orientationAttachment.uint32Value) ?? .up
|
||||
}
|
||||
}
|
||||
if let data = serializePixelBuffer(buffer: pixelBuffer) {
|
||||
self.screencastBufferClientContext?.setCurrentFrame(data: data)
|
||||
self.screencastBufferClientContext?.setCurrentFrame(data: data, orientation: orientation)
|
||||
}
|
||||
|
||||
//self.videoCapturer?.injectSampleBuffer(sampleBuffer)
|
||||
|
@ -72,4 +72,44 @@
|
||||
<key>UIPrerenderedIcon</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>New1</key>
|
||||
<dict>
|
||||
<key>CFBundleIconFiles</key>
|
||||
<array>
|
||||
<string>New1_20x20</string>
|
||||
<string>New1_29x29</string>
|
||||
<string>New1_40x40</string>
|
||||
<string>New1_58x58</string>
|
||||
<string>New1_60x60</string>
|
||||
<string>New1_76x76</string>
|
||||
<string>New1_80x80</string>
|
||||
<string>New1_87x87</string>
|
||||
<string>New1_120x120</string>
|
||||
<string>New1_152x152</string>
|
||||
<string>New1_167x167</string>
|
||||
<string>New1_180x180</string>
|
||||
</array>
|
||||
<key>UIPrerenderedIcon</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>New2</key>
|
||||
<dict>
|
||||
<key>CFBundleIconFiles</key>
|
||||
<array>
|
||||
<string>New2_20x20</string>
|
||||
<string>New2_29x29</string>
|
||||
<string>New2_40x40</string>
|
||||
<string>New2_58x58</string>
|
||||
<string>New2_60x60</string>
|
||||
<string>New2_76x76</string>
|
||||
<string>New2_80x80</string>
|
||||
<string>New2_87x87</string>
|
||||
<string>New2_120x120</string>
|
||||
<string>New2_152x152</string>
|
||||
<string>New2_167x167</string>
|
||||
<string>New2_180x180</string>
|
||||
</array>
|
||||
<key>UIPrerenderedIcon</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
@ -66,4 +66,44 @@
|
||||
<key>UIPrerenderedIcon</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>New1</key>
|
||||
<dict>
|
||||
<key>CFBundleIconFiles</key>
|
||||
<array>
|
||||
<string>New1_20x20</string>
|
||||
<string>New1_29x29</string>
|
||||
<string>New1_40x40</string>
|
||||
<string>New1_58x58</string>
|
||||
<string>New1_60x60</string>
|
||||
<string>New1_76x76</string>
|
||||
<string>New1_80x80</string>
|
||||
<string>New1_87x87</string>
|
||||
<string>New1_120x120</string>
|
||||
<string>New1_152x152</string>
|
||||
<string>New1_167x167</string>
|
||||
<string>New1_180x180</string>
|
||||
</array>
|
||||
<key>UIPrerenderedIcon</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>New2</key>
|
||||
<dict>
|
||||
<key>CFBundleIconFiles</key>
|
||||
<array>
|
||||
<string>New2_20x20</string>
|
||||
<string>New2_29x29</string>
|
||||
<string>New2_40x40</string>
|
||||
<string>New2_58x58</string>
|
||||
<string>New2_60x60</string>
|
||||
<string>New2_76x76</string>
|
||||
<string>New2_80x80</string>
|
||||
<string>New2_87x87</string>
|
||||
<string>New2_120x120</string>
|
||||
<string>New2_152x152</string>
|
||||
<string>New2_167x167</string>
|
||||
<string>New2_180x180</string>
|
||||
</array>
|
||||
<key>UIPrerenderedIcon</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
BIN
Telegram/Telegram-iOS/New1_120x120.png
Normal file
After Width: | Height: | Size: 8.5 KiB |
BIN
Telegram/Telegram-iOS/New1_152x152.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
Telegram/Telegram-iOS/New1_167x167.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
Telegram/Telegram-iOS/New1_180x180.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
Telegram/Telegram-iOS/New1_20x20.png
Normal file
After Width: | Height: | Size: 889 B |
BIN
Telegram/Telegram-iOS/New1_29x29.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
Telegram/Telegram-iOS/New1_40x40.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
Telegram/Telegram-iOS/New1_58x58.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
Telegram/Telegram-iOS/New1_60x60.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
Telegram/Telegram-iOS/New1_76x76.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
Telegram/Telegram-iOS/New1_80x80.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
Telegram/Telegram-iOS/New1_87x87.png
Normal file
After Width: | Height: | Size: 5.5 KiB |
BIN
Telegram/Telegram-iOS/New2_120x120.png
Normal file
After Width: | Height: | Size: 8.9 KiB |
BIN
Telegram/Telegram-iOS/New2_152x152.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
Telegram/Telegram-iOS/New2_167x167.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
Telegram/Telegram-iOS/New2_180x180.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
Telegram/Telegram-iOS/New2_20x20.png
Normal file
After Width: | Height: | Size: 917 B |
BIN
Telegram/Telegram-iOS/New2_29x29.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
Telegram/Telegram-iOS/New2_40x40.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
Telegram/Telegram-iOS/New2_58x58.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
Telegram/Telegram-iOS/New2_60x60.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
Telegram/Telegram-iOS/New2_76x76.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
Telegram/Telegram-iOS/New2_80x80.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
Telegram/Telegram-iOS/New2_87x87.png
Normal file
After Width: | Height: | Size: 5.8 KiB |
@ -3948,6 +3948,7 @@ Unused sets are archived when you add more.";
|
||||
"WallpaperPreview.Title" = "Background Preview";
|
||||
"WallpaperPreview.PreviewTopText" = "Press Set to apply the background";
|
||||
"WallpaperPreview.PreviewBottomText" = "Enjoy the view";
|
||||
|
||||
"WallpaperPreview.SwipeTopText" = "Swipe left or right to preview more backgrounds";
|
||||
"WallpaperPreview.SwipeBottomText" = "Backgrounds for the god of backgrounds!";
|
||||
"WallpaperPreview.SwipeColorsTopText" = "Swipe left or right to see more colors";
|
||||
@ -4428,6 +4429,8 @@ Sorry for the inconvenience.";
|
||||
"Appearance.AppIconClassicX" = "Classic X";
|
||||
"Appearance.AppIconFilled" = "Filled";
|
||||
"Appearance.AppIconFilledX" = "Filled X";
|
||||
"Appearance.AppIconNew1" = "Sunset";
|
||||
"Appearance.AppIconNew2" = "Aqua";
|
||||
|
||||
"Appearance.ThemeCarouselClassic" = "Classic";
|
||||
"Appearance.ThemeCarouselDay" = "Day";
|
||||
@ -6465,6 +6468,73 @@ Sorry for the inconvenience.";
|
||||
"VoiceChat.VideoPreviewDescription" = "Are you sure you want to share your video?";
|
||||
"VoiceChat.VideoPreviewShareCamera" = "Share Camera Video";
|
||||
"VoiceChat.VideoPreviewShareScreen" = "Share Screen";
|
||||
"VoiceChat.VideoPreviewStopScreenSharing" = "Stop Screen Sharing";
|
||||
|
||||
"VoiceChat.TapToViewCameraVideo" = "Tap to view camera video";
|
||||
"VoiceChat.TapToViewScreenVideo" = "Tap to view screen sharing";
|
||||
|
||||
"VoiceChat.ShareScreen" = "Share Screen";
|
||||
"VoiceChat.StopScreenSharing" = "Stop Screen Sharing";
|
||||
"VoiceChat.ParticipantIsSpeaking" = "%1$@ is speaking";
|
||||
|
||||
"WallpaperPreview.WallpaperColors" = "Colors";
|
||||
|
||||
"VoiceChat.UnmuteSuggestion" = "You are on mute. Tap here to speak.";
|
||||
|
||||
"VoiceChat.ContextAudio" = "Audio";
|
||||
|
||||
"VoiceChat.VideoPaused" = "Video is paused";
|
||||
"VoiceChat.YouAreSharingScreen" = "You are sharing your screen";
|
||||
"VoiceChat.StopScreenSharingShort" = "Stop Sharing";
|
||||
|
||||
"VoiceChat.OpenGroup" = "Open Group";
|
||||
|
||||
"VoiceChat.NoiseSuppression" = "Noise Suppression";
|
||||
"VoiceChat.NoiseSuppressionEnabled" = "Enabled";
|
||||
"VoiceChat.NoiseSuppressionDisabled" = "Disabled";
|
||||
|
||||
"VoiceChat.Unpin" = "Unpin";
|
||||
|
||||
"VoiceChat.VideoParticipantsLimitExceeded" = "Video is only available\nfor the first %@ members";
|
||||
|
||||
"ImportStickerPack.StickerCount_1" = "1 Sticker";
|
||||
"ImportStickerPack.StickerCount_2" = "2 Stickers";
|
||||
"ImportStickerPack.StickerCount_3_10" = "%@ Stickers";
|
||||
"ImportStickerPack.StickerCount_any" = "%@ Stickers";
|
||||
"ImportStickerPack.StickerCount_many" = "%@ Stickers";
|
||||
"ImportStickerPack.StickerCount_0" = "%@ Stickers";
|
||||
"ImportStickerPack.CreateStickerSet" = "Create Sticker Set";
|
||||
"ImportStickerPack.RemoveFromImport" = "Remove From Import";
|
||||
"ImportStickerPack.ChooseName" = "Choose Name";
|
||||
"ImportStickerPack.ChooseNameDescription" = "Please choose a name for your set.";
|
||||
"ImportStickerPack.NamePlaceholder" = "Name";
|
||||
"ImportStickerPack.GeneratingLink" = "generating link...";
|
||||
"ImportStickerPack.CheckingLink" = "checking availability...";
|
||||
"ImportStickerPack.ChooseLink" = "Choose Link";
|
||||
"ImportStickerPack.ChooseLinkDescription" = "You can use a-z, 0-9 and underscores.";
|
||||
"ImportStickerPack.LinkTaken" = "Sorry, this link is already taken.";
|
||||
"ImportStickerPack.LinkAvailable" = "Link is available.";
|
||||
"ImportStickerPack.ImportingStickers" = "Importing Stickers";
|
||||
"ImportStickerPack.Of" = "%1$@ of %2$@ Imported";
|
||||
"ImportStickerPack.InProgress" = "Please keep this window open\nuntil the import is completed.";
|
||||
"ImportStickerPack.Create" = "Create";
|
||||
|
||||
"WallpaperPreview.PreviewBottomTextAnimatable" = "Tap the play button to view the background animation.";
|
||||
|
||||
"Conversation.InputMenu" = "Menu";
|
||||
"Conversation.MessageDoesntExist" = "Message doesn't exist";
|
||||
|
||||
"Settings.CheckPasswordTitle" = "Is %@ still your number?";
|
||||
"Settings.CheckPasswordText" = "Your account is protected by 2-Step Verification. Do you still remember your password?";
|
||||
"Settings.KeepPassword" = "Yes, definitely";
|
||||
"Settings.TryEnterPassword" = "Not sure, let me try";
|
||||
|
||||
"TwoFactorSetup.PasswordRecovery.Title" = "Create New Password";
|
||||
"TwoFactorSetup.PasswordRecovery.Text" = "You have successfully reset your password.\nPlease enter a new password to continue";
|
||||
"TwoFactorSetup.PasswordRecovery.PlaceholderPassword" = "New Password";
|
||||
"TwoFactorSetup.PasswordRecovery.PlaceholderConfirmPassword" = "Re-enter New Password";
|
||||
"TwoFactorSetup.PasswordRecovery.Action" = "Continue";
|
||||
"TwoFactorSetup.PasswordRecovery.Skip" = "Skip";
|
||||
"TwoFactorSetup.PasswordRecovery.SkipAlertTitle" = "Attention!";
|
||||
"TwoFactorSetup.PasswordRecovery.SkipAlertText" = "Skipping this step will disable 2-step verification for your account. Are you sure you want to skip?";
|
||||
"TwoFactorSetup.PasswordRecovery.SkipAlertAction" = "Skip";
|
||||
|
@ -56,3 +56,10 @@ http_file(
|
||||
urls = ["https://github.com/Kitware/CMake/releases/download/v3.19.2/cmake-3.19.2-macos-universal.tar.gz"],
|
||||
sha256 = "50afa2cb66bea6a0314ef28034f3ff1647325e30cf5940f97906a56fd9640bd8",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "appcenter_sdk",
|
||||
urls = ["https://github.com/microsoft/appcenter-sdk-apple/releases/download/4.1.1/AppCenter-SDK-Apple-4.1.1.zip"],
|
||||
sha256 = "032907801dc7784744a1ca8fd40d3eecc34a2e27a93a4b3993f617cca204a9f3",
|
||||
build_file = "@//third-party/AppCenter:AppCenter.BUILD",
|
||||
)
|
||||
|
@ -106,6 +106,15 @@ class BazelCommandLine:
|
||||
def set_build_number(self, build_number):
|
||||
self.build_number = build_number
|
||||
|
||||
def set_custom_target(self, target_name):
|
||||
self.custom_target = target_name
|
||||
|
||||
def set_continue_on_error(self, continue_on_error):
|
||||
self.continue_on_error = continue_on_error
|
||||
|
||||
def set_enable_sandbox(self, enable_sandbox):
|
||||
self.enable_sandbox = enable_sandbox
|
||||
|
||||
def set_split_swiftmodules(self, value):
|
||||
self.split_submodules = value
|
||||
|
||||
@ -260,10 +269,18 @@ class BazelCommandLine:
|
||||
self.build_environment.bazel_path
|
||||
]
|
||||
combined_arguments += self.get_startup_bazel_arguments()
|
||||
combined_arguments += [
|
||||
'build',
|
||||
'Telegram/Telegram'
|
||||
]
|
||||
combined_arguments += ['build']
|
||||
|
||||
if self.custom_target is not None:
|
||||
combined_arguments += [self.custom_target]
|
||||
else:
|
||||
combined_arguments += ['Telegram/Telegram']
|
||||
|
||||
if self.continue_on_error:
|
||||
combined_arguments += ['--keep_going']
|
||||
|
||||
if self.enable_sandbox:
|
||||
combined_arguments += ['--spawn_strategy=sandboxed']
|
||||
|
||||
if self.configuration_path is None:
|
||||
raise Exception('configuration_path is not defined')
|
||||
@ -353,10 +370,15 @@ def generate_project(arguments):
|
||||
bazel_command_line.set_build_number(arguments.buildNumber)
|
||||
|
||||
disable_extensions = False
|
||||
disable_provisioning_profiles = False
|
||||
generate_dsym = False
|
||||
|
||||
if arguments.disableExtensions is not None:
|
||||
disable_extensions = arguments.disableExtensions
|
||||
if arguments.disableProvisioningProfiles is not None:
|
||||
disable_provisioning_profiles = arguments.disableProvisioningProfiles
|
||||
if arguments.generateDsym is not None:
|
||||
generate_dsym = arguments.generateDsym
|
||||
|
||||
call_executable(['killall', 'Xcode'], check_result=False)
|
||||
|
||||
@ -364,6 +386,7 @@ def generate_project(arguments):
|
||||
build_environment=bazel_command_line.build_environment,
|
||||
disable_extensions=disable_extensions,
|
||||
disable_provisioning_profiles=disable_provisioning_profiles,
|
||||
generate_dsym=generate_dsym,
|
||||
configuration_path=bazel_command_line.configuration_path,
|
||||
bazel_app_arguments=bazel_command_line.get_project_generation_arguments()
|
||||
)
|
||||
@ -386,6 +409,9 @@ def build(arguments):
|
||||
|
||||
bazel_command_line.set_configuration(arguments.configuration)
|
||||
bazel_command_line.set_build_number(arguments.buildNumber)
|
||||
bazel_command_line.set_custom_target(arguments.target)
|
||||
bazel_command_line.set_continue_on_error(arguments.continueOnError)
|
||||
bazel_command_line.set_enable_sandbox(arguments.sandbox)
|
||||
|
||||
bazel_command_line.set_split_swiftmodules(not arguments.disableParallelSwiftmoduleGeneration)
|
||||
|
||||
@ -512,6 +538,15 @@ if __name__ == '__main__':
|
||||
'''
|
||||
)
|
||||
|
||||
generateProjectParser.add_argument(
|
||||
'--generateDsym',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='''
|
||||
This improves profiling experinence by generating DSYM files. Keep disabled for better build performance.
|
||||
'''
|
||||
)
|
||||
|
||||
buildParser = subparsers.add_parser('build', help='Build the app')
|
||||
buildParser.add_argument(
|
||||
'--buildNumber',
|
||||
@ -540,6 +575,24 @@ if __name__ == '__main__':
|
||||
default=False,
|
||||
help='Generate .swiftmodule files in parallel to building modules, can speed up compilation on multi-core systems.'
|
||||
)
|
||||
buildParser.add_argument(
|
||||
'--target',
|
||||
type=str,
|
||||
help='A custom bazel target name to build.',
|
||||
metavar='target_name'
|
||||
)
|
||||
buildParser.add_argument(
|
||||
'--continueOnError',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Continue build process after an error.',
|
||||
)
|
||||
buildParser.add_argument(
|
||||
'--sandbox',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Enable sandbox.',
|
||||
)
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
parser.print_help()
|
||||
|
@ -10,7 +10,7 @@ def remove_directory(path):
|
||||
shutil.rmtree(path)
|
||||
|
||||
|
||||
def generate(build_environment: BuildEnvironment, disable_extensions, disable_provisioning_profiles, configuration_path, bazel_app_arguments):
|
||||
def generate(build_environment: BuildEnvironment, disable_extensions, disable_provisioning_profiles, generate_dsym, configuration_path, bazel_app_arguments):
|
||||
project_path = os.path.join(build_environment.base_path, 'build-input/gen/project')
|
||||
app_target = 'Telegram'
|
||||
|
||||
@ -81,6 +81,8 @@ def generate(build_environment: BuildEnvironment, disable_extensions, disable_pr
|
||||
bazel_build_arguments += ['--//Telegram:disableExtensions']
|
||||
if disable_provisioning_profiles:
|
||||
bazel_build_arguments += ['--//Telegram:disableProvisioningProfiles']
|
||||
if generate_dsym:
|
||||
bazel_build_arguments += ['--apple_generate_dsym']
|
||||
|
||||
call_executable([
|
||||
tulsi_path,
|
||||
|
@ -8,4 +8,5 @@ exports_files([
|
||||
"WatchApp.mobileprovision",
|
||||
"WatchExtension.mobileprovision",
|
||||
"Widget.mobileprovision",
|
||||
"BroadcastUpload.mobileprovision",
|
||||
])
|
||||
|
@ -1 +1 @@
|
||||
2100
|
||||
2300
|
||||
|
@ -3,84 +3,11 @@
|
||||
set -e
|
||||
set -x
|
||||
|
||||
API_HOST="https://api.appcenter.ms"
|
||||
IPA_PATH="build/artifacts/Telegram.ipa"
|
||||
DSYM_PATH="build/artifacts/Telegram.DSYMs.zip"
|
||||
|
||||
upload_ipa() {
|
||||
GROUP_DATA=$(curl \
|
||||
-X GET \
|
||||
--header "X-API-Token: $API_TOKEN" \
|
||||
"$API_HOST/v0.1/apps/$API_USER_NAME/$API_APP_NAME/distribution_groups/Internal" \
|
||||
)
|
||||
|
||||
GROUP_ID=$(echo "$GROUP_DATA" | python -c 'import json,sys; obj=json.load(sys.stdin); print obj["id"];')
|
||||
|
||||
UPLOAD_TOKEN=$(curl \
|
||||
-X POST \
|
||||
--header "Content-Type: application/json" \
|
||||
--header "Accept: application/json" \
|
||||
--header "X-API-Token: $API_TOKEN" \
|
||||
"$API_HOST/v0.1/apps/$API_USER_NAME/$API_APP_NAME/release_uploads" \
|
||||
)
|
||||
|
||||
|
||||
UPLOAD_URL=$(echo "$UPLOAD_TOKEN" | python -c 'import json,sys; obj=json.load(sys.stdin); print obj["upload_url"];')
|
||||
UPLOAD_ID=$(echo "$UPLOAD_TOKEN" | python -c 'import json,sys; obj=json.load(sys.stdin); print obj["upload_id"];')
|
||||
|
||||
curl --progress-bar -F "ipa=@${IPA_PATH}" "$UPLOAD_URL"
|
||||
|
||||
RELEASE_TOKEN=$(curl \
|
||||
-X PATCH \
|
||||
--header "Content-Type: application/json" \
|
||||
--header "Accept: application/json" \
|
||||
--header "X-API-Token: $API_TOKEN" \
|
||||
-d '{ "status": "committed" }' \
|
||||
"$API_HOST/v0.1/apps/$API_USER_NAME/$API_APP_NAME/release_uploads/$UPLOAD_ID" \
|
||||
)
|
||||
|
||||
|
||||
RELEASE_URL=$(echo "$RELEASE_TOKEN" | python -c 'import json,sys; obj=json.load(sys.stdin); print obj["release_url"];')
|
||||
RELEASE_ID=$(echo "$RELEASE_TOKEN" | python -c 'import json,sys; obj=json.load(sys.stdin); print obj["release_id"];')
|
||||
|
||||
curl \
|
||||
-X POST \
|
||||
--header "Content-Type: application/json" \
|
||||
--header "Accept: application/json" \
|
||||
--header "X-API-Token: $API_TOKEN" \
|
||||
-d "{ \"id\": \"$GROUP_ID\", \"mandatory_update\": false, \"notify_testers\": false }" \
|
||||
"$API_HOST/$RELEASE_URL/groups"
|
||||
}
|
||||
|
||||
upload_dsym() {
|
||||
UPLOAD_DSYM_DATA=$(curl \
|
||||
-X POST \
|
||||
--header "Content-Type: application/json" \
|
||||
--header "Accept: application/json" \
|
||||
--header "X-API-Token: $API_TOKEN" \
|
||||
-d "{ \"symbol_type\": \"Apple\"}" \
|
||||
"$API_HOST/v0.1/apps/$API_USER_NAME/$API_APP_NAME/symbol_uploads" \
|
||||
)
|
||||
|
||||
DSYM_UPLOAD_URL=$(echo "$UPLOAD_DSYM_DATA" | python -c 'import json,sys; obj=json.load(sys.stdin); print obj["upload_url"];')
|
||||
DSYM_UPLOAD_ID=$(echo "$UPLOAD_DSYM_DATA" | python -c 'import json,sys; obj=json.load(sys.stdin); print obj["symbol_upload_id"];')
|
||||
|
||||
curl \
|
||||
--progress-bar \
|
||||
--header "x-ms-blob-type: BlockBlob" \
|
||||
--upload-file "${DSYM_PATH}" \
|
||||
"$DSYM_UPLOAD_URL"
|
||||
|
||||
curl \
|
||||
-X PATCH \
|
||||
--header "Content-Type: application/json" \
|
||||
--header "Accept: application/json" \
|
||||
--header "X-API-Token: $API_TOKEN" \
|
||||
-d '{ "status": "committed" }' \
|
||||
"$API_HOST/v0.1/apps/$API_USER_NAME/$API_APP_NAME/symbol_uploads/$DSYM_UPLOAD_ID"
|
||||
}
|
||||
|
||||
APPCENTER="/usr/local/bin/appcenter"
|
||||
|
||||
$APPCENTER login --token "$API_TOKEN"
|
||||
$APPCENTER distribute release --app "$API_USER_NAME/$API_APP_NAME" -f "$IPA_PATH" -g Internal
|
||||
$APPCENTER crashes upload-symbols --app "$API_USER_NAME/$API_APP_NAME" --symbol "$DSYM_PATH"
|
||||
|
@ -154,9 +154,9 @@ public struct ChatAvailableMessageActions {
|
||||
}
|
||||
|
||||
public enum WallpaperUrlParameter {
|
||||
case slug(String, WallpaperPresentationOptions, UIColor?, UIColor?, Int32?, Int32?)
|
||||
case slug(String, WallpaperPresentationOptions, [UInt32], Int32?, Int32?)
|
||||
case color(UIColor)
|
||||
case gradient(UIColor, UIColor, Int32?)
|
||||
case gradient([UInt32], Int32?)
|
||||
}
|
||||
|
||||
public enum ResolvedUrlSettingsSection {
|
||||
@ -591,7 +591,7 @@ public protocol SharedAccountContext: class {
|
||||
func makeComposeController(context: AccountContext) -> ViewController
|
||||
func makeChatListController(context: AccountContext, groupId: PeerGroupId, controlsHistoryPreload: Bool, hideNetworkActivityStatus: Bool, previewing: Bool, enableDebugActions: Bool) -> ChatListController
|
||||
func makeChatController(context: AccountContext, chatLocation: ChatLocation, subject: ChatControllerSubject?, botStart: ChatControllerInitialBotStart?, mode: ChatControllerPresentationMode) -> ChatController
|
||||
func makeChatMessagePreviewItem(context: AccountContext, messages: [Message], theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder, forcedResourceStatus: FileMediaResourceStatus?, tapMessage: ((Message) -> Void)?, clickThroughMessage: (() -> Void)?) -> ListViewItem
|
||||
func makeChatMessagePreviewItem(context: AccountContext, messages: [Message], theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder, forcedResourceStatus: FileMediaResourceStatus?, tapMessage: ((Message) -> Void)?, clickThroughMessage: (() -> Void)?, backgroundNode: ASDisplayNode?) -> ListViewItem
|
||||
func makeChatMessageDateHeaderItem(context: AccountContext, timestamp: Int32, theme: PresentationTheme, strings: PresentationStrings, wallpaper: TelegramWallpaper, fontSize: PresentationFontSize, chatBubbleCorners: PresentationChatBubbleCorners, dateTimeFormat: PresentationDateTimeFormat, nameOrder: PresentationPersonNameOrder) -> ListViewItemHeader
|
||||
func makePeerSharedMediaController(context: AccountContext, peerId: PeerId) -> ViewController?
|
||||
func makeContactSelectionController(_ params: ContactSelectionControllerParams) -> ContactSelectionController
|
||||
|
@ -109,6 +109,7 @@ public final class PresentationCallVideoView {
|
||||
public let getAspect: () -> CGFloat
|
||||
public let setOnOrientationUpdated: (((Orientation, CGFloat) -> Void)?) -> Void
|
||||
public let setOnIsMirroredUpdated: (((Bool) -> Void)?) -> Void
|
||||
public let updateIsEnabled: (Bool) -> Void
|
||||
|
||||
public init(
|
||||
holder: AnyObject,
|
||||
@ -117,7 +118,8 @@ public final class PresentationCallVideoView {
|
||||
getOrientation: @escaping () -> Orientation,
|
||||
getAspect: @escaping () -> CGFloat,
|
||||
setOnOrientationUpdated: @escaping (((Orientation, CGFloat) -> Void)?) -> Void,
|
||||
setOnIsMirroredUpdated: @escaping (((Bool) -> Void)?) -> Void
|
||||
setOnIsMirroredUpdated: @escaping (((Bool) -> Void)?) -> Void,
|
||||
updateIsEnabled: @escaping (Bool) -> Void
|
||||
) {
|
||||
self.holder = holder
|
||||
self.view = view
|
||||
@ -126,6 +128,7 @@ public final class PresentationCallVideoView {
|
||||
self.getAspect = getAspect
|
||||
self.setOnOrientationUpdated = setOnOrientationUpdated
|
||||
self.setOnIsMirroredUpdated = setOnIsMirroredUpdated
|
||||
self.updateIsEnabled = updateIsEnabled
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,6 +191,7 @@ public struct PresentationGroupCallState: Equatable {
|
||||
public var raisedHand: Bool
|
||||
public var scheduleTimestamp: Int32?
|
||||
public var subscribedToScheduled: Bool
|
||||
public var isVideoEnabled: Bool
|
||||
|
||||
public init(
|
||||
myPeerId: PeerId,
|
||||
@ -200,7 +204,8 @@ public struct PresentationGroupCallState: Equatable {
|
||||
title: String?,
|
||||
raisedHand: Bool,
|
||||
scheduleTimestamp: Int32?,
|
||||
subscribedToScheduled: Bool
|
||||
subscribedToScheduled: Bool,
|
||||
isVideoEnabled: Bool
|
||||
) {
|
||||
self.myPeerId = myPeerId
|
||||
self.networkState = networkState
|
||||
@ -213,6 +218,7 @@ public struct PresentationGroupCallState: Equatable {
|
||||
self.raisedHand = raisedHand
|
||||
self.scheduleTimestamp = scheduleTimestamp
|
||||
self.subscribedToScheduled = subscribedToScheduled
|
||||
self.isVideoEnabled = isVideoEnabled
|
||||
}
|
||||
}
|
||||
|
||||
@ -308,6 +314,61 @@ public enum PresentationGroupCallTone {
|
||||
case recordingStarted
|
||||
}
|
||||
|
||||
public struct PresentationGroupCallRequestedVideo {
|
||||
public enum Quality {
|
||||
case thumbnail
|
||||
case medium
|
||||
case full
|
||||
}
|
||||
|
||||
public struct SsrcGroup {
|
||||
public var semantics: String
|
||||
public var ssrcs: [UInt32]
|
||||
}
|
||||
|
||||
public var audioSsrc: UInt32
|
||||
public var endpointId: String
|
||||
public var ssrcGroups: [SsrcGroup]
|
||||
public var minQuality: Quality
|
||||
public var maxQuality: Quality
|
||||
}
|
||||
|
||||
public extension GroupCallParticipantsContext.Participant {
|
||||
var videoEndpointId: String? {
|
||||
return self.videoDescription?.endpointId
|
||||
}
|
||||
|
||||
var presentationEndpointId: String? {
|
||||
return self.presentationDescription?.endpointId
|
||||
}
|
||||
}
|
||||
|
||||
public extension GroupCallParticipantsContext.Participant {
|
||||
func requestedVideoChannel(minQuality: PresentationGroupCallRequestedVideo.Quality, maxQuality: PresentationGroupCallRequestedVideo.Quality) -> PresentationGroupCallRequestedVideo? {
|
||||
guard let audioSsrc = self.ssrc else {
|
||||
return nil
|
||||
}
|
||||
guard let videoDescription = self.videoDescription else {
|
||||
return nil
|
||||
}
|
||||
return PresentationGroupCallRequestedVideo(audioSsrc: audioSsrc, endpointId: videoDescription.endpointId, ssrcGroups: videoDescription.ssrcGroups.map { group in
|
||||
PresentationGroupCallRequestedVideo.SsrcGroup(semantics: group.semantics, ssrcs: group.ssrcs)
|
||||
}, minQuality: minQuality, maxQuality: maxQuality)
|
||||
}
|
||||
|
||||
func requestedPresentationVideoChannel(minQuality: PresentationGroupCallRequestedVideo.Quality, maxQuality: PresentationGroupCallRequestedVideo.Quality) -> PresentationGroupCallRequestedVideo? {
|
||||
guard let audioSsrc = self.ssrc else {
|
||||
return nil
|
||||
}
|
||||
guard let presentationDescription = self.presentationDescription else {
|
||||
return nil
|
||||
}
|
||||
return PresentationGroupCallRequestedVideo(audioSsrc: audioSsrc, endpointId: presentationDescription.endpointId, ssrcGroups: presentationDescription.ssrcGroups.map { group in
|
||||
PresentationGroupCallRequestedVideo.SsrcGroup(semantics: group.semantics, ssrcs: group.ssrcs)
|
||||
}, minQuality: minQuality, maxQuality: maxQuality)
|
||||
}
|
||||
}
|
||||
|
||||
public protocol PresentationGroupCall: class {
|
||||
var account: Account { get }
|
||||
var accountContext: AccountContext { get }
|
||||
@ -321,8 +382,10 @@ public protocol PresentationGroupCall: class {
|
||||
|
||||
var audioOutputState: Signal<([AudioSessionOutput], AudioSessionOutput?), NoError> { get }
|
||||
|
||||
var isSpeaking: Signal<Bool, NoError> { get }
|
||||
var canBeRemoved: Signal<Bool, NoError> { get }
|
||||
var state: Signal<PresentationGroupCallState, NoError> { get }
|
||||
var stateVersion: Signal<Int, NoError> { get }
|
||||
var summaryState: Signal<PresentationGroupCallSummaryState?, NoError> { get }
|
||||
var members: Signal<PresentationGroupCallMembers?, NoError> { get }
|
||||
var audioLevels: Signal<[(PeerId, UInt32, Float, Bool)], NoError> { get }
|
||||
@ -352,7 +415,7 @@ public protocol PresentationGroupCall: class {
|
||||
func switchVideoCamera()
|
||||
func updateDefaultParticipantsAreMuted(isMuted: Bool)
|
||||
func setVolume(peerId: PeerId, volume: Int32, sync: Bool)
|
||||
func setFullSizeVideo(endpointId: String?)
|
||||
func setRequestedVideoList(items: [PresentationGroupCallRequestedVideo])
|
||||
func setCurrentAudioOutput(_ output: AudioSessionOutput)
|
||||
|
||||
func playTone(_ tone: PresentationGroupCallTone)
|
||||
@ -368,10 +431,8 @@ public protocol PresentationGroupCall: class {
|
||||
|
||||
var inviteLinks: Signal<GroupCallInviteLinks?, NoError> { get }
|
||||
|
||||
var incomingVideoSources: Signal<Set<String>, NoError> { get }
|
||||
|
||||
func makeIncomingVideoView(endpointId: String, completion: @escaping (PresentationCallVideoView?) -> Void)
|
||||
func makeOutgoingVideoView(completion: @escaping (PresentationCallVideoView?) -> Void)
|
||||
func makeIncomingVideoView(endpointId: String, requestClone: Bool, completion: @escaping (PresentationCallVideoView?, PresentationCallVideoView?) -> Void)
|
||||
func makeOutgoingVideoView(requestClone: Bool, completion: @escaping (PresentationCallVideoView?, PresentationCallVideoView?) -> Void)
|
||||
|
||||
func loadMoreMembers(token: String)
|
||||
}
|
||||
|
@ -61,26 +61,35 @@ public final class AnimatedNavigationStripeNode: ASDisplayNode {
|
||||
private let foregroundLineNode: ASImageNode
|
||||
private var backgroundLineNodes: [Int: BackgroundLineNode] = [:]
|
||||
private var removingBackgroundLineNodes: [BackgroundLineNode] = []
|
||||
|
||||
|
||||
private let maskContainerNode: ASDisplayNode
|
||||
private let topShadowNode: ASImageNode
|
||||
private let bottomShadowNode: ASImageNode
|
||||
private let middleShadowNode: ASDisplayNode
|
||||
|
||||
private var currentForegroundImage: UIImage?
|
||||
private var currentBackgroundImage: UIImage?
|
||||
private var currentClearBackgroundImage: UIImage?
|
||||
|
||||
override public init() {
|
||||
self.maskContainerNode = ASDisplayNode()
|
||||
|
||||
self.foregroundLineNode = ASImageNode()
|
||||
self.topShadowNode = ASImageNode()
|
||||
self.bottomShadowNode = ASImageNode()
|
||||
self.middleShadowNode = ASDisplayNode()
|
||||
self.middleShadowNode.backgroundColor = .white
|
||||
|
||||
super.init()
|
||||
|
||||
self.clipsToBounds = true
|
||||
|
||||
|
||||
self.addSubnode(self.maskContainerNode)
|
||||
self.addSubnode(self.foregroundLineNode)
|
||||
self.addSubnode(self.topShadowNode)
|
||||
self.addSubnode(self.bottomShadowNode)
|
||||
self.maskContainerNode.addSubnode(self.topShadowNode)
|
||||
self.maskContainerNode.addSubnode(self.bottomShadowNode)
|
||||
self.maskContainerNode.addSubnode(self.middleShadowNode)
|
||||
self.layer.mask = self.maskContainerNode.layer
|
||||
}
|
||||
|
||||
public func update(colors: Colors, configuration: Configuration, transition: ContainedViewLayoutTransition) {
|
||||
@ -123,7 +132,7 @@ public final class AnimatedNavigationStripeNode: ASDisplayNode {
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
var locations: [CGFloat] = [1.0, 0.0]
|
||||
let colors: [CGColor] = [colors.clearBackground.cgColor, colors.clearBackground.withAlphaComponent(0.0).cgColor]
|
||||
let colors: [CGColor] = [UIColor.white.withAlphaComponent(0.0).cgColor, UIColor.white.cgColor]
|
||||
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||
@ -135,7 +144,7 @@ public final class AnimatedNavigationStripeNode: ASDisplayNode {
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
var locations: [CGFloat] = [1.0, 0.0]
|
||||
let colors: [CGColor] = [colors.clearBackground.cgColor, colors.clearBackground.withAlphaComponent(0.0).cgColor]
|
||||
let colors: [CGColor] = [UIColor.white.withAlphaComponent(0.0).cgColor, UIColor.white.cgColor]
|
||||
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: &locations)!
|
||||
@ -161,6 +170,8 @@ public final class AnimatedNavigationStripeNode: ASDisplayNode {
|
||||
|
||||
transition.updateFrame(node: self.topShadowNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: 2.0, height: defaultVerticalInset)))
|
||||
transition.updateFrame(node: self.bottomShadowNode, frame: CGRect(origin: CGPoint(x: 0.0, y: configuration.height - defaultVerticalInset), size: CGSize(width: 2.0, height: defaultVerticalInset)))
|
||||
transition.updateFrame(node: self.middleShadowNode, frame: CGRect(origin: CGPoint(x: 0.0, y: defaultVerticalInset), size: CGSize(width: 2.0, height: configuration.height - defaultVerticalInset * 2.0)))
|
||||
transition.updateFrame(node: self.maskContainerNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: 2.0, height: configuration.height)))
|
||||
|
||||
let availableVerticalHeight: CGFloat = configuration.height - defaultVerticalInset * 2.0
|
||||
|
||||
@ -214,7 +225,7 @@ public final class AnimatedNavigationStripeNode: ASDisplayNode {
|
||||
itemNode.overlayNode.image = self.currentClearBackgroundImage
|
||||
self.backgroundLineNodes[index] = itemNode
|
||||
self.insertSubnode(itemNode.lineNode, belowSubnode: self.foregroundLineNode)
|
||||
self.insertSubnode(itemNode.overlayNode, belowSubnode: self.topShadowNode)
|
||||
self.topShadowNode.supernode?.insertSubnode(itemNode.overlayNode, belowSubnode: self.topShadowNode)
|
||||
backgroundItemNodesToOffset.append(itemNode)
|
||||
}
|
||||
itemNodeTransition.updateFrame(node: itemNode.lineNode, frame: itemFrame, beginWithCurrentState: true)
|
||||
|
@ -614,7 +614,7 @@ private final class AnimatedStickerDirectFrameSource: AnimatedStickerFrameSource
|
||||
self.data = data
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.bytesPerRow = (4 * Int(width) + 15) & (~15)
|
||||
self.bytesPerRow = DeviceGraphicsContextSettings.shared.bytesPerRow(forWidth: Int(width))
|
||||
self.currentFrame = 0
|
||||
let rawData = TGGUnzipData(data, 8 * 1024 * 1024) ?? data
|
||||
let decompressedData = transformedWithFitzModifier(data: rawData, fitzModifier: fitzModifier)
|
||||
|
@ -13,7 +13,7 @@ final class SoftwareAnimationRenderer: ASDisplayNode, AnimationRenderer {
|
||||
queue.async { [weak self] in
|
||||
switch type {
|
||||
case .argb:
|
||||
let calculatedBytesPerRow = (4 * Int(width) + 15) & (~15)
|
||||
let calculatedBytesPerRow = DeviceGraphicsContextSettings.shared.bytesPerRow(forWidth: Int(width))
|
||||
assert(bytesPerRow == calculatedBytesPerRow)
|
||||
case .yuva:
|
||||
break
|
||||
|
@ -6,7 +6,7 @@
|
||||
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ASCGImageBuffer.h"
|
||||
#import <AsyncDisplayKit/ASCGImageBuffer.h>
|
||||
|
||||
#import <sys/mman.h>
|
||||
#import <mach/mach_init.h>
|
||||
@ -32,7 +32,7 @@
|
||||
{
|
||||
if (self = [super init]) {
|
||||
_length = length;
|
||||
_isVM = (length >= vm_page_size);
|
||||
_isVM = false;//(length >= vm_page_size);
|
||||
if (_isVM) {
|
||||
_mutableBytes = mmap(NULL, length, PROT_WRITE | PROT_READ, MAP_ANONYMOUS | MAP_PRIVATE, VM_MAKE_TAG(VM_MEMORY_COREGRAPHICS_DATA), 0);
|
||||
if (_mutableBytes == MAP_FAILED) {
|
||||
@ -43,7 +43,7 @@
|
||||
|
||||
// Check the VM flag again because we may have failed above.
|
||||
if (!_isVM) {
|
||||
_mutableBytes = calloc(1, length);
|
||||
_mutableBytes = malloc(length);
|
||||
}
|
||||
}
|
||||
return self;
|
||||
|
@ -733,9 +733,6 @@
|
||||
range.location = range.location + range.length - 1;
|
||||
range.length = 1;
|
||||
[self.textView scrollRangeToVisible:range];
|
||||
|
||||
CGPoint bottomOffset = CGPointMake(0, self.textView.contentSize.height - self.textView.bounds.size.height);
|
||||
//[self.textView setContentOffset:bottomOffset animated:NO];
|
||||
}
|
||||
|
||||
#pragma mark - Keyboard
|
||||
|
@ -7,7 +7,7 @@
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASGraphicsContext.h>
|
||||
#import "ASCGImageBuffer.h"
|
||||
#import <AsyncDisplayKit/ASCGImageBuffer.h>
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
#import <AsyncDisplayKit/ASConfigurationInternal.h>
|
||||
#import <AsyncDisplayKit/ASInternalHelpers.h>
|
||||
|
@ -13,7 +13,7 @@ public final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDeco
|
||||
private var displayLinkAnimator: ConstantDisplayLinkAnimator?
|
||||
|
||||
private var audioLevel: CGFloat = 0
|
||||
private var presentationAudioLevel: CGFloat = 0
|
||||
public var presentationAudioLevel: CGFloat = 0
|
||||
|
||||
private(set) var isAnimating = false
|
||||
|
||||
@ -94,6 +94,10 @@ public final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDeco
|
||||
}
|
||||
|
||||
public func updateLevel(_ level: CGFloat) {
|
||||
self.updateLevel(level, immediately: false)
|
||||
}
|
||||
|
||||
public func updateLevel(_ level: CGFloat, immediately: Bool = false) {
|
||||
let normalizedLevel = min(1, max(level / maxLevel, 0))
|
||||
|
||||
smallBlob.updateSpeedLevel(to: normalizedLevel)
|
||||
@ -101,14 +105,26 @@ public final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDeco
|
||||
bigBlob.updateSpeedLevel(to: normalizedLevel)
|
||||
|
||||
audioLevel = normalizedLevel
|
||||
if immediately {
|
||||
presentationAudioLevel = normalizedLevel
|
||||
}
|
||||
}
|
||||
|
||||
public func startAnimating() {
|
||||
self.startAnimating(immediately: false)
|
||||
}
|
||||
|
||||
public func startAnimating(immediately: Bool = false) {
|
||||
guard !isAnimating else { return }
|
||||
isAnimating = true
|
||||
|
||||
mediumBlob.layer.animateScale(from: 0.5, to: 1, duration: 0.15, removeOnCompletion: false)
|
||||
bigBlob.layer.animateScale(from: 0.5, to: 1, duration: 0.15, removeOnCompletion: false)
|
||||
if !immediately {
|
||||
mediumBlob.layer.animateScale(from: 0.75, to: 1, duration: 0.35, removeOnCompletion: false)
|
||||
bigBlob.layer.animateScale(from: 0.75, to: 1, duration: 0.35, removeOnCompletion: false)
|
||||
} else {
|
||||
mediumBlob.layer.removeAllAnimations()
|
||||
bigBlob.layer.removeAllAnimations()
|
||||
}
|
||||
|
||||
updateBlobsState()
|
||||
|
||||
@ -123,8 +139,8 @@ public final class VoiceBlobView: UIView, TGModernConversationInputMicButtonDeco
|
||||
guard isAnimating else { return }
|
||||
isAnimating = false
|
||||
|
||||
mediumBlob.layer.animateScale(from: 1.0, to: 0.5, duration: duration, removeOnCompletion: false)
|
||||
bigBlob.layer.animateScale(from: 1.0, to: 0.5, duration: duration, removeOnCompletion: false)
|
||||
mediumBlob.layer.animateScale(from: 1.0, to: 0.75, duration: duration, removeOnCompletion: false)
|
||||
bigBlob.layer.animateScale(from: 1.0, to: 0.75, duration: duration, removeOnCompletion: false)
|
||||
|
||||
updateBlobsState()
|
||||
|
||||
@ -176,11 +192,13 @@ final class BlobView: UIView {
|
||||
|
||||
var level: CGFloat = 0 {
|
||||
didSet {
|
||||
CATransaction.begin()
|
||||
CATransaction.setDisableActions(true)
|
||||
let lv = minScale + (maxScale - minScale) * level
|
||||
shapeLayer.transform = CATransform3DMakeScale(lv, lv, 1)
|
||||
CATransaction.commit()
|
||||
if abs(self.level - oldValue) > 0.01 {
|
||||
CATransaction.begin()
|
||||
CATransaction.setDisableActions(true)
|
||||
let lv = self.minScale + (self.maxScale - self.minScale) * self.level
|
||||
self.shapeLayer.transform = CATransform3DMakeScale(lv, lv, 1)
|
||||
CATransaction.commit()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -247,7 +265,7 @@ final class BlobView: UIView {
|
||||
|
||||
layer.addSublayer(shapeLayer)
|
||||
|
||||
shapeLayer.transform = CATransform3DMakeScale(minScale, minScale, 1)
|
||||
self.shapeLayer.transform = CATransform3DMakeScale(minScale, minScale, 1)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
@ -255,75 +273,59 @@ final class BlobView: UIView {
|
||||
}
|
||||
|
||||
func setColor(_ color: UIColor, animated: Bool) {
|
||||
let previousColor = shapeLayer.fillColor
|
||||
shapeLayer.fillColor = color.cgColor
|
||||
let previousColor = self.shapeLayer.fillColor
|
||||
self.shapeLayer.fillColor = color.cgColor
|
||||
if animated, let previousColor = previousColor {
|
||||
shapeLayer.animate(from: previousColor, to: color.cgColor, keyPath: "fillColor", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.3)
|
||||
self.shapeLayer.animate(from: previousColor, to: color.cgColor, keyPath: "fillColor", timingFunction: CAMediaTimingFunctionName.linear.rawValue, duration: 0.3)
|
||||
}
|
||||
}
|
||||
|
||||
func updateSpeedLevel(to newSpeedLevel: CGFloat) {
|
||||
speedLevel = max(speedLevel, newSpeedLevel)
|
||||
self.speedLevel = max(self.speedLevel, newSpeedLevel)
|
||||
|
||||
if abs(lastSpeedLevel - newSpeedLevel) > 0.5 {
|
||||
animateToNewShape()
|
||||
}
|
||||
// if abs(lastSpeedLevel - newSpeedLevel) > 0.5 {
|
||||
// animateToNewShape()
|
||||
// }
|
||||
}
|
||||
|
||||
func startAnimating() {
|
||||
animateToNewShape()
|
||||
self.animateToNewShape()
|
||||
}
|
||||
|
||||
func stopAnimating() {
|
||||
fromPoints = currentPoints
|
||||
toPoints = nil
|
||||
pop_removeAnimation(forKey: "blob")
|
||||
self.shapeLayer.removeAnimation(forKey: "path")
|
||||
}
|
||||
|
||||
private func animateToNewShape() {
|
||||
guard !isCircle else { return }
|
||||
|
||||
if pop_animation(forKey: "blob") != nil {
|
||||
fromPoints = currentPoints
|
||||
toPoints = nil
|
||||
pop_removeAnimation(forKey: "blob")
|
||||
if self.shapeLayer.path == nil {
|
||||
let points = generateNextBlob(for: self.bounds.size)
|
||||
self.shapeLayer.path = UIBezierPath.smoothCurve(through: points, length: bounds.width, smoothness: smoothness).cgPath
|
||||
}
|
||||
|
||||
if fromPoints == nil {
|
||||
fromPoints = generateNextBlob(for: bounds.size)
|
||||
}
|
||||
if toPoints == nil {
|
||||
toPoints = generateNextBlob(for: bounds.size)
|
||||
}
|
||||
let nextPoints = generateNextBlob(for: self.bounds.size)
|
||||
let nextPath = UIBezierPath.smoothCurve(through: nextPoints, length: bounds.width, smoothness: smoothness).cgPath
|
||||
|
||||
let animation = POPBasicAnimation()
|
||||
animation.property = POPAnimatableProperty.property(withName: "blob.transition", initializer: { property in
|
||||
property?.readBlock = { blobView, values in
|
||||
guard let blobView = blobView as? BlobView, let values = values else { return }
|
||||
|
||||
values.pointee = blobView.transition
|
||||
}
|
||||
property?.writeBlock = { blobView, values in
|
||||
guard let blobView = blobView as? BlobView, let values = values else { return }
|
||||
|
||||
blobView.transition = values.pointee
|
||||
}
|
||||
}) as? POPAnimatableProperty
|
||||
animation.completionBlock = { [weak self] animation, finished in
|
||||
let animation = CABasicAnimation(keyPath: "path")
|
||||
let previousPath = self.shapeLayer.path
|
||||
self.shapeLayer.path = nextPath
|
||||
animation.duration = CFTimeInterval(1 / (self.minSpeed + (self.maxSpeed - self.minSpeed) * self.speedLevel))
|
||||
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
|
||||
animation.fromValue = previousPath
|
||||
animation.toValue = nextPath
|
||||
animation.isRemovedOnCompletion = false
|
||||
animation.fillMode = .forwards
|
||||
animation.completion = { [weak self] finished in
|
||||
if finished {
|
||||
self?.fromPoints = self?.currentPoints
|
||||
self?.toPoints = nil
|
||||
self?.animateToNewShape()
|
||||
}
|
||||
}
|
||||
animation.duration = CFTimeInterval(1 / (minSpeed + (maxSpeed - minSpeed) * speedLevel))
|
||||
animation.timingFunction = CAMediaTimingFunction(name: .linear)
|
||||
animation.fromValue = 0
|
||||
animation.toValue = 1
|
||||
pop_add(animation, forKey: "blob")
|
||||
|
||||
lastSpeedLevel = speedLevel
|
||||
speedLevel = 0
|
||||
|
||||
self.shapeLayer.add(animation, forKey: "path")
|
||||
|
||||
self.lastSpeedLevel = self.speedLevel
|
||||
self.speedLevel = 0
|
||||
}
|
||||
|
||||
// MARK: Helpers
|
||||
|
@ -30,7 +30,7 @@ public final class AuthDataTransferSplashScreen: ViewController {
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
let defaultTheme = NavigationBarTheme(rootControllerTheme: self.presentationData.theme)
|
||||
let navigationBarTheme = NavigationBarTheme(buttonColor: defaultTheme.buttonColor, disabledButtonColor: defaultTheme.disabledButtonColor, primaryTextColor: defaultTheme.primaryTextColor, backgroundColor: .clear, separatorColor: .clear, badgeBackgroundColor: defaultTheme.badgeBackgroundColor, badgeStrokeColor: defaultTheme.badgeStrokeColor, badgeTextColor: defaultTheme.badgeTextColor)
|
||||
let navigationBarTheme = NavigationBarTheme(buttonColor: defaultTheme.buttonColor, disabledButtonColor: defaultTheme.disabledButtonColor, primaryTextColor: defaultTheme.primaryTextColor, backgroundColor: .clear, enableBackgroundBlur: false, separatorColor: .clear, badgeBackgroundColor: defaultTheme.badgeBackgroundColor, badgeStrokeColor: defaultTheme.badgeStrokeColor, badgeTextColor: defaultTheme.badgeTextColor)
|
||||
|
||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(theme: navigationBarTheme, strings: NavigationBarStrings(back: self.presentationData.strings.Common_Back, close: self.presentationData.strings.Common_Close)))
|
||||
|
||||
@ -80,7 +80,7 @@ public final class AuthDataTransferSplashScreen: ViewController {
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
(self.displayNode as! AuthDataTransferSplashScreenNode).containerLayoutUpdated(layout: layout, navigationHeight: self.navigationHeight, transition: transition)
|
||||
(self.displayNode as! AuthDataTransferSplashScreenNode).containerLayoutUpdated(layout: layout, navigationHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -90,7 +90,7 @@ public final class AuthTransferScanScreen: ViewController {
|
||||
|
||||
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)
|
||||
let navigationBarTheme = NavigationBarTheme(buttonColor: .white, disabledButtonColor: .white, primaryTextColor: .white, backgroundColor: .clear, enableBackgroundBlur: false, 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)))
|
||||
|
||||
@ -205,7 +205,7 @@ public final class AuthTransferScanScreen: ViewController {
|
||||
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)
|
||||
(self.displayNode as! AuthTransferScanScreenNode).containerLayoutUpdated(layout: layout, navigationHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -623,11 +623,13 @@ public final class AvatarNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
public func drawPeerAvatarLetters(context: CGContext, size: CGSize, font: UIFont, letters: [String], peerId: PeerId) {
|
||||
context.beginPath()
|
||||
context.addEllipse(in: CGRect(x: 0.0, y: 0.0, width: size.width, height:
|
||||
size.height))
|
||||
context.clip()
|
||||
public func drawPeerAvatarLetters(context: CGContext, size: CGSize, round: Bool = true, font: UIFont, letters: [String], peerId: PeerId) {
|
||||
if round {
|
||||
context.beginPath()
|
||||
context.addEllipse(in: CGRect(x: 0.0, y: 0.0, width: size.width, height:
|
||||
size.height))
|
||||
context.clip()
|
||||
}
|
||||
|
||||
let colorIndex: Int
|
||||
if peerId.namespace == .max {
|
||||
|
@ -87,12 +87,25 @@ public func peerAvatarImageData(account: Account, peerReference: PeerReference?,
|
||||
}
|
||||
}
|
||||
|
||||
public func peerAvatarCompleteImage(account: Account, peer: Peer, size: CGSize) -> Signal<UIImage?, NoError> {
|
||||
public func peerAvatarCompleteImage(account: Account, peer: Peer, size: CGSize, round: Bool = true, font: UIFont = avatarPlaceholderFont(size: 13.0), drawLetters: Bool = true, fullSize: Bool = false, blurred: Bool = false) -> Signal<UIImage?, NoError> {
|
||||
let iconSignal: Signal<UIImage?, NoError>
|
||||
if let signal = peerAvatarImage(account: account, peerReference: PeerReference(peer), authorOfMessage: nil, representation: peer.profileImageRepresentations.first, displayDimensions: size, inset: 0.0, emptyColor: nil, synchronousLoad: false) {
|
||||
iconSignal = signal
|
||||
if let signal = peerAvatarImage(account: account, peerReference: PeerReference(peer), authorOfMessage: nil, representation: peer.profileImageRepresentations.first, displayDimensions: size, round: round, blurred: blurred, inset: 0.0, emptyColor: nil, synchronousLoad: fullSize) {
|
||||
if fullSize, let fullSizeSignal = peerAvatarImage(account: account, peerReference: PeerReference(peer), authorOfMessage: nil, representation: peer.profileImageRepresentations.last, displayDimensions: size, emptyColor: nil, synchronousLoad: true) {
|
||||
iconSignal = combineLatest(.single(nil) |> then(signal), .single(nil) |> then(fullSizeSignal))
|
||||
|> mapToSignal { thumbnailImage, fullSizeImage -> Signal<UIImage?, NoError> in
|
||||
if let fullSizeImage = fullSizeImage {
|
||||
return .single(fullSizeImage.0)
|
||||
} else if let thumbnailImage = thumbnailImage {
|
||||
return .single(thumbnailImage.0)
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
iconSignal = signal
|
||||
|> map { imageVersions -> UIImage? in
|
||||
return imageVersions?.0
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let peerId = peer.id
|
||||
@ -100,10 +113,17 @@ public func peerAvatarCompleteImage(account: Account, peer: Peer, size: CGSize)
|
||||
if displayLetters.count == 2 && displayLetters[0].isSingleEmoji && displayLetters[1].isSingleEmoji {
|
||||
displayLetters = [displayLetters[0]]
|
||||
}
|
||||
if !drawLetters {
|
||||
displayLetters = []
|
||||
}
|
||||
iconSignal = Signal { subscriber in
|
||||
let image = generateImage(size, rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
drawPeerAvatarLetters(context: context, size: CGSize(width: size.width, height: size.height), font: avatarPlaceholderFont(size: 13.0), letters: displayLetters, peerId: peerId)
|
||||
drawPeerAvatarLetters(context: context, size: CGSize(width: size.width, height: size.height), round: round, font: font, letters: displayLetters, peerId: peerId)
|
||||
if blurred {
|
||||
context.setFillColor(UIColor(rgb: 0x000000, alpha: 0.5).cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
}
|
||||
})?.withRenderingMode(.alwaysOriginal)
|
||||
|
||||
subscriber.putNext(image)
|
||||
@ -114,7 +134,7 @@ public func peerAvatarCompleteImage(account: Account, peer: Peer, size: CGSize)
|
||||
return iconSignal
|
||||
}
|
||||
|
||||
public func peerAvatarImage(account: Account, peerReference: PeerReference?, authorOfMessage: MessageReference?, representation: TelegramMediaImageRepresentation?, displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0), round: Bool = true, inset: CGFloat = 0.0, emptyColor: UIColor? = nil, synchronousLoad: Bool = false, provideUnrounded: Bool = false) -> Signal<(UIImage, UIImage)?, NoError>? {
|
||||
public func peerAvatarImage(account: Account, peerReference: PeerReference?, authorOfMessage: MessageReference?, representation: TelegramMediaImageRepresentation?, displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0), round: Bool = true, blurred: Bool = false, inset: CGFloat = 0.0, emptyColor: UIColor? = nil, synchronousLoad: Bool = false, provideUnrounded: Bool = false) -> Signal<(UIImage, UIImage)?, NoError>? {
|
||||
if let imageData = peerAvatarImageData(account: account, peerReference: peerReference, authorOfMessage: authorOfMessage, representation: representation, synchronousLoad: synchronousLoad) {
|
||||
return imageData
|
||||
|> mapToSignal { data -> Signal<(UIImage, UIImage)?, NoError> in
|
||||
@ -133,19 +153,42 @@ public func peerAvatarImage(account: Account, peerReference: PeerReference?, aut
|
||||
context.clip()
|
||||
}
|
||||
|
||||
var shouldBlur = false
|
||||
if case .blurred = dataType {
|
||||
let imageContextSize = CGSize(width: 64.0, height: 64.0)
|
||||
let imageContext = DrawingContext(size: imageContextSize, scale: 1.0, premultiplied: true, clear: true)
|
||||
shouldBlur = true
|
||||
} else if blurred {
|
||||
shouldBlur = true
|
||||
}
|
||||
if shouldBlur {
|
||||
let imageContextSize = size.width > 200.0 ? CGSize(width: 192.0, height: 192.0) : CGSize(width: 64.0, height: 64.0)
|
||||
let imageContext = DrawingContext(size: imageContextSize, scale: 1.0, clear: true)
|
||||
imageContext.withFlippedContext { c in
|
||||
c.draw(dataImage, in: CGRect(origin: CGPoint(), size: imageContextSize))
|
||||
|
||||
context.setBlendMode(.saturation)
|
||||
context.setFillColor(UIColor(rgb: 0xffffff, alpha: 1.0).cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
context.setBlendMode(.copy)
|
||||
}
|
||||
|
||||
telegramFastBlurMore(Int32(imageContext.size.width * imageContext.scale), Int32(imageContext.size.height * imageContext.scale), Int32(imageContext.bytesPerRow), imageContext.bytes)
|
||||
|
||||
if size.width > 200.0 {
|
||||
telegramFastBlurMore(Int32(imageContext.size.width * imageContext.scale), Int32(imageContext.size.height * imageContext.scale), Int32(imageContext.bytesPerRow), imageContext.bytes)
|
||||
telegramFastBlurMore(Int32(imageContext.size.width * imageContext.scale), Int32(imageContext.size.height * imageContext.scale), Int32(imageContext.bytesPerRow), imageContext.bytes)
|
||||
telegramFastBlurMore(Int32(imageContext.size.width * imageContext.scale), Int32(imageContext.size.height * imageContext.scale), Int32(imageContext.bytesPerRow), imageContext.bytes)
|
||||
telegramFastBlurMore(Int32(imageContext.size.width * imageContext.scale), Int32(imageContext.size.height * imageContext.scale), Int32(imageContext.bytesPerRow), imageContext.bytes)
|
||||
}
|
||||
|
||||
dataImage = imageContext.generateImage()!.cgImage!
|
||||
}
|
||||
|
||||
context.draw(dataImage, in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
|
||||
if blurred {
|
||||
context.setBlendMode(.normal)
|
||||
context.setFillColor(UIColor(rgb: 0x000000, alpha: 0.45).cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
context.setBlendMode(.copy)
|
||||
}
|
||||
if round {
|
||||
if displayDimensions.width == 60.0 {
|
||||
context.setBlendMode(.destinationOut)
|
||||
|
@ -115,11 +115,7 @@ public final class BotCheckoutController: ViewController {
|
||||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
let displayNode = BotCheckoutControllerNode(controller: self, navigationBar: self.navigationBar!, updateNavigationOffset: { [weak self] offset in
|
||||
if let strongSelf = self {
|
||||
strongSelf.navigationOffset = offset
|
||||
}
|
||||
}, context: self.context, invoice: self.invoice, messageId: self.messageId, inputData: self.inputData, present: { [weak self] c, a in
|
||||
let displayNode = BotCheckoutControllerNode(controller: self, navigationBar: self.navigationBar!, context: self.context, invoice: self.invoice, messageId: self.messageId, inputData: self.inputData, present: { [weak self] c, a in
|
||||
self?.present(c, in: .window(.root), with: a)
|
||||
}, dismissAnimated: { [weak self] in
|
||||
self?.dismiss()
|
||||
@ -150,7 +146,7 @@ public final class BotCheckoutController: ViewController {
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition, additionalInsets: UIEdgeInsets())
|
||||
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition, additionalInsets: UIEdgeInsets())
|
||||
}
|
||||
|
||||
@objc private func cancelPressed() {
|
||||
|
@ -538,7 +538,7 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz
|
||||
private var passwordTip: String?
|
||||
private var passwordTipDisposable: Disposable?
|
||||
|
||||
init(controller: BotCheckoutController?, navigationBar: NavigationBar, updateNavigationOffset: @escaping (CGFloat) -> Void, context: AccountContext, invoice: TelegramMediaInvoice, messageId: MessageId, inputData: Promise<BotCheckoutController.InputData?>, present: @escaping (ViewController, Any?) -> Void, dismissAnimated: @escaping () -> Void, completed: @escaping (String, MessageId?) -> Void) {
|
||||
init(controller: BotCheckoutController?, navigationBar: NavigationBar, context: AccountContext, invoice: TelegramMediaInvoice, messageId: MessageId, inputData: Promise<BotCheckoutController.InputData?>, present: @escaping (ViewController, Any?) -> Void, dismissAnimated: @escaping () -> Void, completed: @escaping (String, MessageId?) -> Void) {
|
||||
self.controller = controller
|
||||
self.context = context
|
||||
self.messageId = messageId
|
||||
@ -585,7 +585,7 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz
|
||||
}
|
||||
|
||||
self.actionButtonPanelNode = ASDisplayNode()
|
||||
self.actionButtonPanelNode.backgroundColor = self.presentationData.theme.rootController.navigationBar.backgroundColor
|
||||
self.actionButtonPanelNode.backgroundColor = self.presentationData.theme.rootController.navigationBar.opaqueBackgroundColor
|
||||
|
||||
self.actionButtonPanelSeparator = ASDisplayNode()
|
||||
self.actionButtonPanelSeparator.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor
|
||||
@ -598,7 +598,7 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz
|
||||
self.inProgressDimNode.isUserInteractionEnabled = false
|
||||
self.inProgressDimNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor.withAlphaComponent(0.5)
|
||||
|
||||
super.init(controller: nil, navigationBar: navigationBar, updateNavigationOffset: updateNavigationOffset, state: signal)
|
||||
super.init(controller: nil, navigationBar: navigationBar, state: signal)
|
||||
|
||||
self.arguments = arguments
|
||||
|
||||
@ -952,7 +952,7 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz
|
||||
switch value {
|
||||
case .notSet:
|
||||
break
|
||||
case let .set(hint, _, _, _):
|
||||
case let .set(hint, _, _, _, _):
|
||||
if !hint.isEmpty {
|
||||
strongSelf.passwordTip = hint
|
||||
}
|
||||
@ -1192,12 +1192,8 @@ final class BotCheckoutControllerNode: ItemListControllerNode, PKPaymentAuthoriz
|
||||
}
|
||||
|
||||
if !liabilityNoticeAccepted {
|
||||
let messageId = self.messageId
|
||||
let botPeer: Signal<Peer?, NoError> = self.context.account.postbox.transaction { transaction -> Peer? in
|
||||
if let message = transaction.getMessage(messageId) {
|
||||
return message.author
|
||||
}
|
||||
return nil
|
||||
return transaction.getPeer(paymentForm.paymentBotId)
|
||||
}
|
||||
let _ = (combineLatest(ApplicationSpecificNotice.getBotPaymentLiability(accountManager: self.context.sharedContext.accountManager, peerId: paymentForm.paymentBotId), botPeer, self.context.account.postbox.loadedPeerWithId(paymentForm.providerId))
|
||||
|> deliverOnMainQueue).start(next: { [weak self] value, botPeer, providerPeer in
|
||||
|
@ -144,7 +144,7 @@ final class BotCheckoutInfoController: ViewController {
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition)
|
||||
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
|
||||
}
|
||||
|
||||
@objc func cancelPressed() {
|
||||
|
@ -142,7 +142,7 @@ final class BotCheckoutNativeCardEntryController: ViewController {
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition)
|
||||
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
|
||||
}
|
||||
|
||||
@objc func cancelPressed() {
|
||||
|
@ -75,7 +75,7 @@ final class BotCheckoutWebInteractionController: ViewController {
|
||||
override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition)
|
||||
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
|
||||
}
|
||||
|
||||
override var presentationController: UIPresentationController? {
|
||||
|
@ -48,11 +48,7 @@ public final class BotReceiptController: ViewController {
|
||||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
let displayNode = BotReceiptControllerNode(controller: nil, navigationBar: self.navigationBar!, updateNavigationOffset: { [weak self] offset in
|
||||
if let strongSelf = self {
|
||||
strongSelf.navigationOffset = offset
|
||||
}
|
||||
}, context: self.context, messageId: self.messageId, dismissAnimated: { [weak self] in
|
||||
let displayNode = BotReceiptControllerNode(controller: nil, navigationBar: self.navigationBar!, context: self.context, messageId: self.messageId, dismissAnimated: { [weak self] in
|
||||
self?.dismiss()
|
||||
})
|
||||
|
||||
@ -79,7 +75,7 @@ public final class BotReceiptController: ViewController {
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition, additionalInsets: UIEdgeInsets())
|
||||
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition, additionalInsets: UIEdgeInsets())
|
||||
}
|
||||
|
||||
@objc private func cancelPressed() {
|
||||
|
@ -280,7 +280,7 @@ final class BotReceiptControllerNode: ItemListControllerNode {
|
||||
private let actionButtonPanelSeparator: ASDisplayNode
|
||||
private let actionButton: BotCheckoutActionButton
|
||||
|
||||
init(controller: ItemListController?, navigationBar: NavigationBar, updateNavigationOffset: @escaping (CGFloat) -> Void, context: AccountContext, messageId: MessageId, dismissAnimated: @escaping () -> Void) {
|
||||
init(controller: ItemListController?, navigationBar: NavigationBar, context: AccountContext, messageId: MessageId, dismissAnimated: @escaping () -> Void) {
|
||||
self.context = context
|
||||
self.dismissAnimated = dismissAnimated
|
||||
|
||||
@ -296,7 +296,7 @@ final class BotReceiptControllerNode: ItemListControllerNode {
|
||||
}
|
||||
|
||||
self.actionButtonPanelNode = ASDisplayNode()
|
||||
self.actionButtonPanelNode.backgroundColor = self.presentationData.theme.rootController.navigationBar.backgroundColor
|
||||
self.actionButtonPanelNode.backgroundColor = self.presentationData.theme.rootController.navigationBar.opaqueBackgroundColor
|
||||
|
||||
self.actionButtonPanelSeparator = ASDisplayNode()
|
||||
self.actionButtonPanelSeparator.backgroundColor = self.presentationData.theme.rootController.navigationBar.separatorColor
|
||||
@ -304,7 +304,7 @@ final class BotReceiptControllerNode: ItemListControllerNode {
|
||||
self.actionButton = BotCheckoutActionButton(activeFillColor: self.presentationData.theme.list.itemAccentColor, foregroundColor: self.presentationData.theme.list.plainBackgroundColor)
|
||||
self.actionButton.setState(.active(self.presentationData.strings.Common_Done))
|
||||
|
||||
super.init(controller: controller, navigationBar: navigationBar, updateNavigationOffset: updateNavigationOffset, state: signal)
|
||||
super.init(controller: controller, navigationBar: navigationBar, state: signal)
|
||||
|
||||
self.dataRequestDisposable = (context.engine.payments.requestBotPaymentReceipt(messageId: messageId) |> deliverOnMainQueue).start(next: { [weak self] receipt in
|
||||
if let strongSelf = self {
|
||||
|
@ -273,7 +273,7 @@ public final class CallListController: TelegramBaseController {
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition)
|
||||
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
|
||||
}
|
||||
|
||||
@objc func callPressed() {
|
||||
|
@ -226,7 +226,7 @@ final class CallListControllerNode: ASDisplayNode {
|
||||
self.openInfo = openInfo
|
||||
self.emptyStateUpdated = emptyStateUpdated
|
||||
|
||||
self.currentState = CallListNodeState(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, disableAnimations: presentationData.disableAnimations, editing: false, messageIdWithRevealedOptions: nil)
|
||||
self.currentState = CallListNodeState(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, disableAnimations: true, editing: false, messageIdWithRevealedOptions: nil)
|
||||
self.statePromise = ValuePromise(self.currentState, ignoreRepeated: true)
|
||||
|
||||
self.listNode = ListView()
|
||||
@ -633,7 +633,7 @@ final class CallListControllerNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
func updateThemeAndStrings(presentationData: PresentationData) {
|
||||
if presentationData.theme !== self.currentState.presentationData.theme || presentationData.strings !== self.currentState.presentationData.strings || presentationData.disableAnimations != self.currentState.disableAnimations {
|
||||
if presentationData.theme !== self.currentState.presentationData.theme || presentationData.strings !== self.currentState.presentationData.strings {
|
||||
self.presentationData = presentationData
|
||||
|
||||
self.leftOverlayNode.backgroundColor = presentationData.theme.list.blocksBackgroundColor
|
||||
@ -652,7 +652,7 @@ final class CallListControllerNode: ASDisplayNode {
|
||||
self.updateEmptyPlaceholder(theme: presentationData.theme, strings: presentationData.strings, type: self.currentLocationAndType.type, isHidden: self.emptyTextNode.alpha.isZero)
|
||||
|
||||
self.updateState {
|
||||
return $0.withUpdatedPresentationData(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, disableAnimations: presentationData.disableAnimations)
|
||||
return $0.withUpdatedPresentationData(presentationData: ItemListPresentationData(presentationData), dateTimeFormat: presentationData.dateTimeFormat, disableAnimations: true)
|
||||
}
|
||||
|
||||
self.listNode.forEachItemHeaderNode({ itemHeaderNode in
|
||||
|
@ -814,7 +814,7 @@ public final class ChatImportActivityScreen: ViewController {
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
self.controllerNode.containerLayoutUpdated(layout, navigationHeight: self.navigationHeight, transition: transition)
|
||||
self.controllerNode.containerLayoutUpdated(layout, navigationHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
|
||||
}
|
||||
|
||||
private func beginImport() {
|
||||
|
@ -93,25 +93,28 @@ public struct ChatEditMessageState: PostboxCoding, Equatable {
|
||||
|
||||
public struct ChatInterfaceMessageActionsState: PostboxCoding, Equatable {
|
||||
public var closedButtonKeyboardMessageId: MessageId?
|
||||
public var dismissedButtonKeyboardMessageId: MessageId?
|
||||
public var processedSetupReplyMessageId: MessageId?
|
||||
public var closedPinnedMessageId: MessageId?
|
||||
public var closedPeerSpecificPackSetup: Bool = false
|
||||
public var dismissedAddContactPhoneNumber: String?
|
||||
|
||||
public var isEmpty: Bool {
|
||||
return self.closedButtonKeyboardMessageId == nil && self.processedSetupReplyMessageId == nil && self.closedPinnedMessageId == nil && self.closedPeerSpecificPackSetup == false && self.dismissedAddContactPhoneNumber == nil
|
||||
return self.closedButtonKeyboardMessageId == nil && self.dismissedButtonKeyboardMessageId == nil && self.processedSetupReplyMessageId == nil && self.closedPinnedMessageId == nil && self.closedPeerSpecificPackSetup == false && self.dismissedAddContactPhoneNumber == nil
|
||||
}
|
||||
|
||||
public init() {
|
||||
self.closedButtonKeyboardMessageId = nil
|
||||
self.dismissedButtonKeyboardMessageId = nil
|
||||
self.processedSetupReplyMessageId = nil
|
||||
self.closedPinnedMessageId = nil
|
||||
self.closedPeerSpecificPackSetup = false
|
||||
self.dismissedAddContactPhoneNumber = nil
|
||||
}
|
||||
|
||||
public init(closedButtonKeyboardMessageId: MessageId?, processedSetupReplyMessageId: MessageId?, closedPinnedMessageId: MessageId?, closedPeerSpecificPackSetup: Bool, dismissedAddContactPhoneNumber: String?) {
|
||||
public init(closedButtonKeyboardMessageId: MessageId?, dismissedButtonKeyboardMessageId: MessageId?, processedSetupReplyMessageId: MessageId?, closedPinnedMessageId: MessageId?, closedPeerSpecificPackSetup: Bool, dismissedAddContactPhoneNumber: String?) {
|
||||
self.closedButtonKeyboardMessageId = closedButtonKeyboardMessageId
|
||||
self.dismissedButtonKeyboardMessageId = dismissedButtonKeyboardMessageId
|
||||
self.processedSetupReplyMessageId = processedSetupReplyMessageId
|
||||
self.closedPinnedMessageId = closedPinnedMessageId
|
||||
self.closedPeerSpecificPackSetup = closedPeerSpecificPackSetup
|
||||
@ -124,6 +127,12 @@ public struct ChatInterfaceMessageActionsState: PostboxCoding, Equatable {
|
||||
} else {
|
||||
self.closedButtonKeyboardMessageId = nil
|
||||
}
|
||||
|
||||
if let messageIdPeerId = decoder.decodeOptionalInt64ForKey("dismissedbuttons.p"), let messageIdNamespace = decoder.decodeOptionalInt32ForKey("dismissedbuttons.n"), let messageIdId = decoder.decodeOptionalInt32ForKey("dismissedbuttons.i") {
|
||||
self.dismissedButtonKeyboardMessageId = MessageId(peerId: PeerId(messageIdPeerId), namespace: messageIdNamespace, id: messageIdId)
|
||||
} else {
|
||||
self.dismissedButtonKeyboardMessageId = nil
|
||||
}
|
||||
|
||||
if let processedMessageIdPeerId = decoder.decodeOptionalInt64ForKey("pb.p"), let processedMessageIdNamespace = decoder.decodeOptionalInt32ForKey("pb.n"), let processedMessageIdId = decoder.decodeOptionalInt32ForKey("pb.i") {
|
||||
self.processedSetupReplyMessageId = MessageId(peerId: PeerId(processedMessageIdPeerId), namespace: processedMessageIdNamespace, id: processedMessageIdId)
|
||||
@ -150,6 +159,16 @@ public struct ChatInterfaceMessageActionsState: PostboxCoding, Equatable {
|
||||
encoder.encodeNil(forKey: "cb.n")
|
||||
encoder.encodeNil(forKey: "cb.i")
|
||||
}
|
||||
|
||||
if let dismissedButtonKeyboardMessageId = self.dismissedButtonKeyboardMessageId {
|
||||
encoder.encodeInt64(dismissedButtonKeyboardMessageId.peerId.toInt64(), forKey: "dismissedbuttons.p")
|
||||
encoder.encodeInt32(dismissedButtonKeyboardMessageId.namespace, forKey: "dismissedbuttons.n")
|
||||
encoder.encodeInt32(dismissedButtonKeyboardMessageId.id, forKey: "dismissedbuttons.i")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "dismissedbuttons.p")
|
||||
encoder.encodeNil(forKey: "dismissedbuttons.n")
|
||||
encoder.encodeNil(forKey: "dismissedbuttons.i")
|
||||
}
|
||||
|
||||
if let processedSetupReplyMessageId = self.processedSetupReplyMessageId {
|
||||
encoder.encodeInt64(processedSetupReplyMessageId.peerId.toInt64(), forKey: "pb.p")
|
||||
|
@ -186,7 +186,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
title = filter.title
|
||||
} else if self.groupId == .root {
|
||||
title = self.presentationData.strings.DialogList_Title
|
||||
self.navigationBar?.item = nil
|
||||
} else {
|
||||
title = self.presentationData.strings.ChatList_ArchivedChatsTitle
|
||||
}
|
||||
@ -366,6 +365,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
strongSelf.navigationItem.setLeftBarButton(editItem, animated: true)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let editItem = UIBarButtonItem(title: strongSelf.presentationData.strings.Common_Edit, style: .plain, target: self, action: #selector(strongSelf.editPressed))
|
||||
editItem.accessibilityLabel = strongSelf.presentationData.strings.Common_Edit
|
||||
strongSelf.navigationItem.setRightBarButton(editItem, animated: true)
|
||||
}
|
||||
|
||||
let (hasProxy, connectsViaProxy) = proxy
|
||||
@ -1134,7 +1137,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
return
|
||||
}
|
||||
let count = ChatControllerCount.with({ $0 })
|
||||
if count != 0 {
|
||||
if count > 1 {
|
||||
strongSelf.present(textAlertController(context: strongSelf.context, title: "", text: "ChatControllerCount \(count)", actions: [TextAlertAction(type: .defaultAction, title: "OK", action: {})]), in: .window(.root))
|
||||
}
|
||||
})
|
||||
@ -1214,7 +1217,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
}
|
||||
}))
|
||||
|
||||
self.suggestAutoarchiveDisposable.set((getServerProvidedSuggestions(postbox: self.context.account.postbox)
|
||||
self.suggestAutoarchiveDisposable.set((getServerProvidedSuggestions(account: self.context.account)
|
||||
|> deliverOnMainQueue).start(next: { [weak self] values in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
@ -1400,8 +1403,10 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
tabContainerOffset += layout.statusBarHeight ?? 0.0
|
||||
tabContainerOffset += 44.0 + 20.0
|
||||
}
|
||||
|
||||
let navigationBarHeight = self.navigationBar?.frame.maxY ?? 0.0
|
||||
|
||||
transition.updateFrame(node: self.tabContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: self.visualNavigationInsetHeight - self.additionalHeight - 46.0 + tabContainerOffset), size: CGSize(width: layout.size.width, height: 46.0)))
|
||||
transition.updateFrame(node: self.tabContainerNode, frame: CGRect(origin: CGPoint(x: 0.0, y: navigationBarHeight - self.additionalNavigationBarHeight - 46.0 + tabContainerOffset), size: CGSize(width: layout.size.width, height: 46.0)))
|
||||
self.tabContainerNode.update(size: CGSize(width: layout.size.width, height: 46.0), sideInset: layout.safeInsets.left, filters: self.tabContainerData?.0 ?? [], selectedFilter: self.chatListDisplayNode.containerNode.currentItemFilter, isReordering: self.chatListDisplayNode.isReorderingFilters || (self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !self.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: false, transitionFraction: self.chatListDisplayNode.containerNode.transitionFraction, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring))
|
||||
if let tabContainerData = self.tabContainerData {
|
||||
self.chatListDisplayNode.inlineTabContainerNode.isHidden = !tabContainerData.1 || tabContainerData.0.count <= 1
|
||||
@ -1410,7 +1415,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
||||
}
|
||||
self.chatListDisplayNode.inlineTabContainerNode.update(size: CGSize(width: layout.size.width, height: 40.0), sideInset: layout.safeInsets.left, filters: self.tabContainerData?.0 ?? [], selectedFilter: self.chatListDisplayNode.containerNode.currentItemFilter, isReordering: self.chatListDisplayNode.isReorderingFilters || (self.chatListDisplayNode.containerNode.currentItemNode.currentState.editing && !self.chatListDisplayNode.didBeginSelectingChatsWhileEditing), isEditing: false, transitionFraction: self.chatListDisplayNode.containerNode.transitionFraction, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring))
|
||||
|
||||
self.chatListDisplayNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationInsetHeight, visualNavigationHeight: self.visualNavigationInsetHeight, cleanNavigationBarHeight: self.cleanNavigationHeight, transition: transition)
|
||||
self.chatListDisplayNode.containerLayoutUpdated(layout, navigationBarHeight: self.cleanNavigationHeight, visualNavigationHeight: navigationBarHeight, cleanNavigationBarHeight: self.cleanNavigationHeight, transition: transition)
|
||||
}
|
||||
|
||||
override public func navigationStackConfigurationUpdated(next: [ViewController]) {
|
||||
|
@ -275,7 +275,7 @@ private final class ChatListContainerItemNode: ASDisplayNode {
|
||||
self.becameEmpty = becameEmpty
|
||||
self.emptyAction = emptyAction
|
||||
|
||||
self.listNode = ChatListNode(context: context, groupId: groupId, chatListFilter: filter, previewing: previewing, fillPreloadItems: controlsHistoryPreload, mode: .chatList, theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: presentationData.disableAnimations)
|
||||
self.listNode = ChatListNode(context: context, groupId: groupId, chatListFilter: filter, previewing: previewing, fillPreloadItems: controlsHistoryPreload, mode: .chatList, theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: true)
|
||||
|
||||
super.init()
|
||||
|
||||
@ -372,7 +372,7 @@ private final class ChatListContainerItemNode: ASDisplayNode {
|
||||
return presentationData.strings.VoiceOver_ScrollStatus(row, count).0
|
||||
}
|
||||
|
||||
self.listNode.updateThemeAndStrings(theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: presentationData.disableAnimations)
|
||||
self.listNode.updateThemeAndStrings(theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: true)
|
||||
|
||||
self.emptyNode?.updateThemeAndStrings(theme: presentationData.theme, strings: presentationData.strings)
|
||||
}
|
||||
|
@ -211,7 +211,7 @@ final class ChatListSearchFiltersContainerNode: ASDisplayNode {
|
||||
let previousContentWidth = self.scrollNode.view.contentSize.width
|
||||
|
||||
if self.currentParams?.presentationData.theme !== presentationData.theme {
|
||||
self.backgroundColor = presentationData.theme.rootController.navigationBar.backgroundColor
|
||||
//self.backgroundColor = presentationData.theme.rootController.navigationBar.opaqueBackgroundColor
|
||||
self.selectedLineNode.image = generateImage(CGSize(width: 5.0, height: 3.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(presentationData.theme.list.itemAccentColor.cgColor)
|
||||
|
@ -515,7 +515,7 @@ public enum ChatListSearchEntry: Comparable, Identifiable {
|
||||
let header = ChatListSearchItemHeader(type: .messages, theme: presentationData.theme, strings: presentationData.strings, actionTitle: nil, action: nil)
|
||||
let selection: ChatHistoryMessageSelection = selected.flatMap { .selectable(selected: $0) } ?? .none
|
||||
if let tagMask = tagMask, tagMask != .photoOrVideo {
|
||||
return ListMessageItem(presentationData: ChatPresentationData(theme: ChatPresentationThemeData(theme: presentationData.theme, wallpaper: .builtin(WallpaperSettings())), fontSize: presentationData.fontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: presentationData.disableAnimations, largeEmoji: false, chatBubbleCorners: PresentationChatBubbleCorners(mainRadius: 0.0, auxiliaryRadius: 0.0, mergeBubbleCorners: false)), context: context, chatLocation: .peer(peer.peerId), interaction: listInteraction, message: message, selection: selection, displayHeader: enableHeaders && !displayCustomHeader, customHeader: nil, hintIsLink: tagMask == .webPage, isGlobalSearchResult: true)
|
||||
return ListMessageItem(presentationData: ChatPresentationData(theme: ChatPresentationThemeData(theme: presentationData.theme, wallpaper: .builtin(WallpaperSettings())), fontSize: presentationData.fontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: true, largeEmoji: false, chatBubbleCorners: PresentationChatBubbleCorners(mainRadius: 0.0, auxiliaryRadius: 0.0, mergeBubbleCorners: false)), context: context, chatLocation: .peer(peer.peerId), interaction: listInteraction, message: message, selection: selection, displayHeader: enableHeaders && !displayCustomHeader, customHeader: nil, hintIsLink: tagMask == .webPage, isGlobalSearchResult: true)
|
||||
} else {
|
||||
return ChatListItem(presentationData: presentationData, context: context, peerGroupId: .root, filterData: nil, index: ChatListIndex(pinningIndex: nil, messageIndex: message.index), content: .peer(messages: [message], peer: peer, combinedReadState: readState, isRemovedFromTotalUnreadCount: false, presence: nil, summaryInfo: ChatListMessageTagSummaryInfo(), embeddedState: nil, inputActivities: nil, promoInfo: nil, ignoreUnreadBadge: true, displayAsMessage: false, hasFailedMessages: false), editing: false, hasActiveRevealControls: false, selected: false, header: tagMask == nil ? header : nil, enableContextActions: false, hiddenOffset: false, interaction: interaction)
|
||||
}
|
||||
@ -737,7 +737,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.presentationData = presentationData
|
||||
self.presentationDataPromise.set(.single(ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: self.presentationData.disableAnimations)))
|
||||
self.presentationDataPromise.set(.single(ChatListPresentationData(theme: self.presentationData.theme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameSortOrder: self.presentationData.nameSortOrder, nameDisplayOrder: self.presentationData.nameDisplayOrder, disableAnimations: true)))
|
||||
|
||||
self.searchStatePromise.set(self.searchStateValue)
|
||||
self.selectedMessages = interaction.getSelectedMessageIds()
|
||||
@ -1502,7 +1502,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
|> deliverOnMainQueue).start(next: { [weak self] presentationData in
|
||||
if let strongSelf = self {
|
||||
strongSelf.presentationData = presentationData
|
||||
strongSelf.presentationDataPromise.set(.single(ChatListPresentationData(theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: presentationData.disableAnimations)))
|
||||
strongSelf.presentationDataPromise.set(.single(ChatListPresentationData(theme: presentationData.theme, fontSize: presentationData.listsFontSize, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameSortOrder: presentationData.nameSortOrder, nameDisplayOrder: presentationData.nameDisplayOrder, disableAnimations: true)))
|
||||
|
||||
strongSelf.listNode.forEachItemHeaderNode({ itemHeaderNode in
|
||||
if let itemHeaderNode = itemHeaderNode as? ChatListSearchItemHeaderNode {
|
||||
@ -1721,7 +1721,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
})
|
||||
}
|
||||
|
||||
let mediaAccessoryPanel = MediaNavigationAccessoryPanel(context: self.context)
|
||||
let mediaAccessoryPanel = MediaNavigationAccessoryPanel(context: self.context, displayBackground: true)
|
||||
mediaAccessoryPanel.containerNode.headerNode.displayScrubber = item.playbackData?.type != .instantVideo
|
||||
mediaAccessoryPanel.close = { [weak self] in
|
||||
if let strongSelf = self, let (_, _, _, _, type, _) = strongSelf.playlistStateAndType {
|
||||
|
@ -121,7 +121,7 @@ final class ChatListSearchMessageSelectionPanelNode: ASDisplayNode {
|
||||
if presentationData.theme !== self.theme {
|
||||
self.theme = presentationData.theme
|
||||
|
||||
self.backgroundNode.backgroundColor = presentationData.theme.rootController.navigationBar.backgroundColor
|
||||
self.backgroundNode.backgroundColor = presentationData.theme.rootController.navigationBar.opaqueBackgroundColor
|
||||
self.separatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor
|
||||
|
||||
self.deleteButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/MessageSelectionTrash"), color: presentationData.theme.chat.inputPanel.panelControlAccentColor), for: [.normal])
|
||||
|
@ -1853,7 +1853,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode {
|
||||
strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: layoutOffset - separatorHeight - topNegativeInset), size: CGSize(width: layout.contentSize.width, height: layout.contentSize.height + separatorHeight + topNegativeInset))
|
||||
|
||||
if let peerPresence = peerPresence as? TelegramUserPresence {
|
||||
strongSelf.peerPresenceManager?.reset(presence: TelegramUserPresence(status: peerPresence.status, lastActivity: 0))
|
||||
strongSelf.peerPresenceManager?.reset(presence: TelegramUserPresence(status: peerPresence.status, lastActivity: 0), isOnline: online)
|
||||
}
|
||||
|
||||
strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset)
|
||||
|
@ -875,7 +875,7 @@ public final class ChatListNode: ListView {
|
||||
|
||||
let removingPeerId = currentRemovingPeerId.with { $0 }
|
||||
|
||||
var disableAnimations = state.presentationData.disableAnimations
|
||||
var disableAnimations = true
|
||||
if previousState.editing != state.editing {
|
||||
disableAnimations = false
|
||||
} else {
|
||||
@ -1332,7 +1332,7 @@ public final class ChatListNode: ListView {
|
||||
}
|
||||
|
||||
public func updateThemeAndStrings(theme: PresentationTheme, fontSize: PresentationFontSize, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameSortOrder: PresentationPersonNameOrder, nameDisplayOrder: PresentationPersonNameOrder, disableAnimations: Bool) {
|
||||
if theme !== self.currentState.presentationData.theme || strings !== self.currentState.presentationData.strings || dateTimeFormat != self.currentState.presentationData.dateTimeFormat || disableAnimations != self.currentState.presentationData.disableAnimations {
|
||||
if theme !== self.currentState.presentationData.theme || strings !== self.currentState.presentationData.strings || dateTimeFormat != self.currentState.presentationData.dateTimeFormat {
|
||||
self.theme = theme
|
||||
if self.keepTopItemOverscrollBackground != nil {
|
||||
self.keepTopItemOverscrollBackground = ListViewKeepTopItemOverscrollBackground(color: theme.chatList.pinnedItemBackgroundColor, direction: true)
|
||||
|
@ -100,7 +100,7 @@ class ChatPlayingActivityContentNode: ChatTitleActivityContentNode {
|
||||
self.addSubnode(self.indicatorNode)
|
||||
}
|
||||
|
||||
override func updateLayout(_ constrainedSize: CGSize, alignment: NSTextAlignment) -> CGSize {
|
||||
override func updateLayout(_ constrainedSize: CGSize, offset: CGFloat, alignment: NSTextAlignment) -> CGSize {
|
||||
let size = self.textNode.updateLayout(constrainedSize)
|
||||
let indicatorSize = CGSize(width: 24.0, height: 16.0)
|
||||
let originX: CGFloat
|
||||
|
@ -72,7 +72,7 @@ class ChatRecordingVideoActivityContentNode: ChatTitleActivityContentNode {
|
||||
self.addSubnode(self.indicatorNode)
|
||||
}
|
||||
|
||||
override func updateLayout(_ constrainedSize: CGSize, alignment: NSTextAlignment) -> CGSize {
|
||||
override func updateLayout(_ constrainedSize: CGSize, offset: CGFloat, alignment: NSTextAlignment) -> CGSize {
|
||||
let size = self.textNode.updateLayout(constrainedSize)
|
||||
let indicatorSize = CGSize(width: 24.0, height: 16.0)
|
||||
let originX: CGFloat
|
||||
|
@ -90,7 +90,7 @@ class ChatRecordingVoiceActivityContentNode: ChatTitleActivityContentNode {
|
||||
self.addSubnode(self.indicatorNode)
|
||||
}
|
||||
|
||||
override func updateLayout(_ constrainedSize: CGSize, alignment: NSTextAlignment) -> CGSize {
|
||||
override func updateLayout(_ constrainedSize: CGSize, offset: CGFloat, alignment: NSTextAlignment) -> CGSize {
|
||||
let size = self.textNode.updateLayout(constrainedSize)
|
||||
let indicatorSize = CGSize(width: 24.0, height: 16.0)
|
||||
let originX: CGFloat
|
||||
@ -99,7 +99,7 @@ class ChatRecordingVoiceActivityContentNode: ChatTitleActivityContentNode {
|
||||
} else {
|
||||
originX = indicatorSize.width
|
||||
}
|
||||
self.textNode.frame = CGRect(origin: CGPoint(x: originX, y: 0.0), size: size)
|
||||
self.textNode.frame = CGRect(origin: CGPoint(x: originX, y: offset), size: size)
|
||||
self.indicatorNode.frame = CGRect(origin: CGPoint(x: self.textNode.frame.minX - indicatorSize.width, y: 0.0), size: indicatorSize)
|
||||
return CGSize(width: size.width + indicatorSize.width, height: size.height)
|
||||
}
|
||||
|
@ -122,13 +122,13 @@ public class ChatTitleActivityContentNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
public func updateLayout(_ constrainedSize: CGSize, alignment: NSTextAlignment) -> CGSize {
|
||||
public func updateLayout(_ constrainedSize: CGSize, offset: CGFloat, alignment: NSTextAlignment) -> CGSize {
|
||||
let size = self.textNode.updateLayout(constrainedSize)
|
||||
self.textNode.bounds = CGRect(origin: CGPoint(), size: size)
|
||||
if case .center = alignment {
|
||||
self.textNode.position = CGPoint(x: 0.0, y: size.height / 2.0)
|
||||
self.textNode.position = CGPoint(x: 0.0, y: size.height / 2.0 + offset)
|
||||
} else {
|
||||
self.textNode.position = CGPoint(x: size.width / 2.0, y: size.height / 2.0)
|
||||
self.textNode.position = CGPoint(x: size.width / 2.0, y: size.height / 2.0 + offset)
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
@ -123,7 +123,7 @@ public class ChatTitleActivityNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
public func updateLayout(_ constrainedSize: CGSize, alignment: NSTextAlignment) -> CGSize {
|
||||
return CGSize(width: 0.0, height: self.contentNode?.updateLayout(constrainedSize, alignment: alignment).height ?? 0.0)
|
||||
public func updateLayout(_ constrainedSize: CGSize, offset: CGFloat = 0.0, alignment: NSTextAlignment) -> CGSize {
|
||||
return CGSize(width: 0.0, height: self.contentNode?.updateLayout(constrainedSize, offset: offset, alignment: alignment).height ?? 0.0)
|
||||
}
|
||||
}
|
||||
|
@ -108,7 +108,7 @@ class ChatTypingActivityContentNode: ChatTitleActivityContentNode {
|
||||
self.addSubnode(self.indicatorNode)
|
||||
}
|
||||
|
||||
override func updateLayout(_ constrainedSize: CGSize, alignment: NSTextAlignment) -> CGSize {
|
||||
override func updateLayout(_ constrainedSize: CGSize, offset: CGFloat, alignment: NSTextAlignment) -> CGSize {
|
||||
let indicatorSize = CGSize(width: 24.0, height: 16.0)
|
||||
let size = self.textNode.updateLayout(CGSize(width: constrainedSize.width - indicatorSize.width, height: constrainedSize.height))
|
||||
var originX: CGFloat
|
||||
|
@ -80,7 +80,7 @@ class ChatUploadingActivityContentNode: ChatTitleActivityContentNode {
|
||||
self.addSubnode(self.indicatorNode)
|
||||
}
|
||||
|
||||
override func updateLayout(_ constrainedSize: CGSize, alignment: NSTextAlignment) -> CGSize {
|
||||
override func updateLayout(_ constrainedSize: CGSize, offset: CGFloat, alignment: NSTextAlignment) -> CGSize {
|
||||
let size = self.textNode.updateLayout(constrainedSize)
|
||||
let indicatorSize = CGSize(width: 24.0, height: 16.0)
|
||||
let originX: CGFloat
|
||||
|
@ -884,7 +884,7 @@ public final class ContactListNode: ASDisplayNode {
|
||||
self.presentationData = presentationData
|
||||
|
||||
self.listNode = ListView()
|
||||
self.listNode.dynamicBounceEnabled = !self.presentationData.disableAnimations
|
||||
self.listNode.dynamicBounceEnabled = false
|
||||
self.listNode.accessibilityPageScrolledString = { row, count in
|
||||
return presentationData.strings.VoiceOver_ScrollStatus(row, count).0
|
||||
}
|
||||
@ -1338,8 +1338,6 @@ public final class ContactListNode: ASDisplayNode {
|
||||
animation = .insertion
|
||||
} else if hadPermissionInfo != hasPermissionInfo {
|
||||
animation = .insertion
|
||||
} else if let previous = previous, !presentationData.disableAnimations, (entries.count - previous.count) < 20 {
|
||||
animation = .default
|
||||
} else {
|
||||
animation = .none
|
||||
}
|
||||
@ -1366,11 +1364,10 @@ public final class ContactListNode: ASDisplayNode {
|
||||
if let strongSelf = self {
|
||||
let previousTheme = strongSelf.presentationData.theme
|
||||
let previousStrings = strongSelf.presentationData.strings
|
||||
let previousDisableAnimations = strongSelf.presentationData.disableAnimations
|
||||
|
||||
strongSelf.presentationData = presentationData
|
||||
|
||||
if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings || previousDisableAnimations != presentationData.disableAnimations {
|
||||
if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings {
|
||||
strongSelf.backgroundColor = presentationData.theme.chatList.backgroundColor
|
||||
strongSelf.listNode.verticalScrollIndicatorColor = presentationData.theme.list.scrollIndicatorColor
|
||||
strongSelf.presentationDataPromise.set(.single(presentationData))
|
||||
@ -1385,7 +1382,7 @@ public final class ContactListNode: ASDisplayNode {
|
||||
strongSelf.authorizationNode.isHidden = authorizationPreviousHidden
|
||||
strongSelf.addSubnode(strongSelf.authorizationNode)
|
||||
|
||||
strongSelf.listNode.dynamicBounceEnabled = !presentationData.disableAnimations
|
||||
strongSelf.listNode.dynamicBounceEnabled = false
|
||||
|
||||
strongSelf.listNode.forEachAccessoryItemNode({ accessoryItemNode in
|
||||
if let accessoryItemNode = accessoryItemNode as? ContactsSectionHeaderAccessoryItemNode {
|
||||
|
@ -419,7 +419,7 @@ public class ContactsController: ViewController {
|
||||
|
||||
self.validLayout = layout
|
||||
|
||||
self.contactsNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationInsetHeight, actualNavigationBarHeight: self.navigationHeight, transition: transition)
|
||||
self.contactsNode.containerLayoutUpdated(layout, navigationBarHeight: self.cleanNavigationHeight, actualNavigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
|
||||
}
|
||||
|
||||
private func activateSearch() {
|
||||
|
@ -199,7 +199,7 @@ public class InviteContactsController: ViewController, MFMessageComposeViewContr
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
self.contactsNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationInsetHeight, actualNavigationBarHeight: self.navigationHeight, transition: transition)
|
||||
self.contactsNode.containerLayoutUpdated(layout, navigationBarHeight: self.cleanNavigationHeight, actualNavigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
|
||||
}
|
||||
|
||||
private func activateSearch() {
|
||||
|
@ -37,7 +37,7 @@ final class InviteContactsCountPanelNode: ASDisplayNode {
|
||||
|
||||
super.init()
|
||||
|
||||
self.backgroundColor = theme.rootController.navigationBar.backgroundColor
|
||||
self.backgroundColor = theme.rootController.navigationBar.opaqueBackgroundColor
|
||||
|
||||
self.addSubnode(self.button)
|
||||
self.addSubnode(self.separatorNode)
|
||||
|
@ -497,7 +497,8 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
|
||||
if let takenViewInfo = takenViewInfo, let parentSupernode = takenViewInfo.contentContainingNode.supernode {
|
||||
self.contentContainerNode.contentNode = .extracted(node: takenViewInfo.contentContainingNode, keepInPlace: source.keepInPlace)
|
||||
if source.keepInPlace {
|
||||
if source.keepInPlace || takenViewInfo.maskView != nil {
|
||||
self.clippingNode.view.mask = takenViewInfo.maskView
|
||||
self.clippingNode.addSubnode(self.contentContainerNode)
|
||||
} else {
|
||||
self.scrollNode.addSubnode(self.contentContainerNode)
|
||||
@ -653,14 +654,26 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
}
|
||||
case let .extracted(extracted, keepInPlace):
|
||||
let springDuration: Double = 0.42 * animationDurationFactor
|
||||
let springDamping: CGFloat = 104.0
|
||||
var springDamping: CGFloat = 104.0
|
||||
if case let .extracted(source) = self.source, source.centerVertically {
|
||||
springDamping = 124.0
|
||||
}
|
||||
|
||||
self.actionsContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2 * animationDurationFactor)
|
||||
self.actionsContainerNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: springDuration, initialVelocity: 0.0, damping: springDamping)
|
||||
|
||||
|
||||
if let originalProjectedContentViewFrame = self.originalProjectedContentViewFrame {
|
||||
let contentParentNode = extracted
|
||||
let localSourceFrame = self.view.convert(originalProjectedContentViewFrame.1, to: self.scrollNode.view)
|
||||
|
||||
var actionsDuration = springDuration
|
||||
var actionsOffset: CGFloat = 0.0
|
||||
var contentDuration = springDuration
|
||||
if case let .extracted(source) = self.source, source.centerVertically {
|
||||
actionsOffset = -(originalProjectedContentViewFrame.1.height - originalProjectedContentViewFrame.0.height) * 0.57
|
||||
actionsDuration *= 1.0
|
||||
contentDuration *= 0.9
|
||||
}
|
||||
|
||||
let localContentSourceFrame: CGRect
|
||||
if keepInPlace {
|
||||
@ -673,9 +686,11 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
reactionContextNode.animateIn(from: CGRect(origin: CGPoint(x: originalProjectedContentViewFrame.1.minX, y: originalProjectedContentViewFrame.1.minY), size: contentParentNode.contentRect.size))
|
||||
}
|
||||
|
||||
self.actionsContainerNode.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: localSourceFrame.center.x - self.actionsContainerNode.position.x, y: localSourceFrame.center.y - self.actionsContainerNode.position.y)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping, additive: true)
|
||||
self.actionsContainerNode.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: localSourceFrame.center.x - self.actionsContainerNode.position.x, y: localSourceFrame.center.y - self.actionsContainerNode.position.y + actionsOffset)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: actionsDuration, initialVelocity: 0.0, damping: springDamping, additive: true)
|
||||
let contentContainerOffset = CGPoint(x: localContentSourceFrame.center.x - self.contentContainerNode.frame.center.x - contentParentNode.contentRect.minX, y: localContentSourceFrame.center.y - self.contentContainerNode.frame.center.y - contentParentNode.contentRect.minY)
|
||||
self.contentContainerNode.layer.animateSpring(from: NSValue(cgPoint: contentContainerOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: springDuration, initialVelocity: 0.0, damping: springDamping, additive: true)
|
||||
self.contentContainerNode.layer.animateSpring(from: NSValue(cgPoint: contentContainerOffset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: contentDuration, initialVelocity: 0.0, damping: springDamping, additive: true, completion: { [weak self] _ in
|
||||
self?.clippingNode.view.mask = nil
|
||||
})
|
||||
contentParentNode.applyAbsoluteOffsetSpring?(-contentContainerOffset.y, springDuration, springDamping)
|
||||
}
|
||||
|
||||
@ -837,11 +852,12 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
updatedContentAreaInScreenSpace.origin.x = 0.0
|
||||
updatedContentAreaInScreenSpace.size.width = self.bounds.width
|
||||
|
||||
self.clippingNode.view.mask = putBackInfo.maskView
|
||||
self.clippingNode.layer.animateFrame(from: self.clippingNode.frame, to: updatedContentAreaInScreenSpace, duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false)
|
||||
self.clippingNode.layer.animateBoundsOriginYAdditive(from: 0.0, to: updatedContentAreaInScreenSpace.minY, duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false)
|
||||
}
|
||||
|
||||
let intermediateCompletion: () -> Void = { [weak contentParentNode] in
|
||||
let intermediateCompletion: () -> Void = { [weak self, weak contentParentNode] in
|
||||
if completedEffect && completedContentNode && completedActionsNode {
|
||||
switch result {
|
||||
case .default, .custom:
|
||||
@ -854,6 +870,8 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
break
|
||||
}
|
||||
|
||||
self?.clippingNode.view.mask = nil
|
||||
|
||||
completion()
|
||||
}
|
||||
}
|
||||
@ -920,7 +938,12 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
localContentSourceFrame = localSourceFrame
|
||||
}
|
||||
|
||||
self.actionsContainerNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: localSourceFrame.center.x - self.actionsContainerNode.position.x, y: localSourceFrame.center.y - self.actionsContainerNode.position.y), duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false, additive: true)
|
||||
var actionsOffset: CGFloat = 0.0
|
||||
if case let .extracted(source) = self.source, source.centerVertically {
|
||||
actionsOffset = -localSourceFrame.width * 0.6
|
||||
}
|
||||
|
||||
self.actionsContainerNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: localSourceFrame.center.x - self.actionsContainerNode.position.x, y: localSourceFrame.center.y - self.actionsContainerNode.position.y + actionsOffset), duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false, additive: true)
|
||||
let contentContainerOffset = CGPoint(x: localContentSourceFrame.center.x - self.contentContainerNode.frame.center.x - contentParentNode.contentRect.minX, y: localContentSourceFrame.center.y - self.contentContainerNode.frame.center.y - contentParentNode.contentRect.minY)
|
||||
self.contentContainerNode.layer.animatePosition(from: CGPoint(), to: contentContainerOffset, duration: transitionDuration * animationDurationFactor, timingFunction: transitionCurve.timingFunction, removeOnCompletion: false, additive: true, completion: { _ in
|
||||
completedContentNode = true
|
||||
@ -1319,6 +1342,10 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
}
|
||||
}
|
||||
case let .extracted(contentParentNode, keepInPlace):
|
||||
var centerVertically = false
|
||||
if case let .extracted(source) = self.source, source.centerVertically {
|
||||
centerVertically = true
|
||||
}
|
||||
let contentActionsSpacing: CGFloat = keepInPlace ? 16.0 : 8.0
|
||||
if let originalProjectedContentViewFrame = self.originalProjectedContentViewFrame {
|
||||
let isInitialLayout = self.actionsContainerNode.frame.size.width.isZero
|
||||
@ -1332,13 +1359,17 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
let maximumActionsFrameOrigin = max(60.0, layout.size.height - layout.intrinsicInsets.bottom - actionsBottomInset - actionsSize.height)
|
||||
let preferredActionsX: CGFloat
|
||||
let originalActionsY: CGFloat
|
||||
if keepInPlace {
|
||||
if centerVertically {
|
||||
originalActionsY = min(originalProjectedContentViewFrame.1.maxY + contentActionsSpacing, maximumActionsFrameOrigin)
|
||||
preferredActionsX = originalProjectedContentViewFrame.1.maxX - actionsSize.width
|
||||
} else if keepInPlace {
|
||||
originalActionsY = originalProjectedContentViewFrame.1.minY - contentActionsSpacing - actionsSize.height
|
||||
preferredActionsX = max(actionsSideInset, originalProjectedContentViewFrame.1.maxX - actionsSize.width)
|
||||
} else {
|
||||
originalActionsY = min(originalProjectedContentViewFrame.1.maxY + contentActionsSpacing, maximumActionsFrameOrigin)
|
||||
preferredActionsX = originalProjectedContentViewFrame.1.minX
|
||||
}
|
||||
|
||||
var originalActionsFrame = CGRect(origin: CGPoint(x: max(actionsSideInset, min(layout.size.width - actionsSize.width - actionsSideInset, preferredActionsX)), y: originalActionsY), size: actionsSize)
|
||||
let originalContentX: CGFloat = originalProjectedContentViewFrame.1.minX
|
||||
let originalContentY: CGFloat
|
||||
@ -1366,7 +1397,20 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
|
||||
var overflowOffset: CGFloat
|
||||
var contentContainerFrame: CGRect
|
||||
if keepInPlace {
|
||||
if centerVertically {
|
||||
overflowOffset = 0.0
|
||||
if layout.size.width > layout.size.height, case .compact = layout.metrics.widthClass {
|
||||
let totalWidth = originalContentFrame.width + originalActionsFrame.width + contentActionsSpacing
|
||||
contentContainerFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - totalWidth) / 2.0 + originalContentFrame.width * 0.1), y: floor((layout.size.height - originalContentFrame.height) / 2.0)), size: originalContentFrame.size)
|
||||
originalActionsFrame.origin.x = contentContainerFrame.maxX + contentActionsSpacing + 14.0
|
||||
originalActionsFrame.origin.y = contentContainerFrame.origin.y
|
||||
contentHeight = layout.size.height
|
||||
} else {
|
||||
let totalHeight = originalContentFrame.height + originalActionsFrame.height
|
||||
contentContainerFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - originalContentFrame.width) / 2.0), y: floor((layout.size.height - totalHeight) / 2.0)), size: originalContentFrame.size)
|
||||
originalActionsFrame.origin.y = contentContainerFrame.maxY + contentActionsSpacing
|
||||
}
|
||||
} else if keepInPlace {
|
||||
overflowOffset = min(0.0, originalActionsFrame.minY - contentTopInset)
|
||||
contentContainerFrame = originalContentFrame.offsetBy(dx: -contentParentNode.contentRect.minX, dy: -contentParentNode.contentRect.minY)
|
||||
if !overflowOffset.isZero {
|
||||
@ -1390,14 +1434,10 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi
|
||||
} else {
|
||||
overflowOffset = min(0.0, originalContentFrame.minY - contentTopInset)
|
||||
contentContainerFrame = originalContentFrame.offsetBy(dx: -contentParentNode.contentRect.minX, dy: -overflowOffset - contentParentNode.contentRect.minY)
|
||||
}
|
||||
|
||||
if case let .extracted(source) = self.source, source.centerVertically {
|
||||
let totalHeight = contentContainerFrame.height + originalActionsFrame.height
|
||||
let updatedOrigin = floor((layout.size.height - totalHeight) / 2.0)
|
||||
let delta = updatedOrigin - contentContainerFrame.origin.y
|
||||
contentContainerFrame.origin.y = updatedOrigin
|
||||
originalActionsFrame.origin.y += delta
|
||||
|
||||
if contentContainerFrame.maxX > layout.size.width {
|
||||
contentContainerFrame = CGRect(origin: CGPoint(x: layout.size.width - contentContainerFrame.width - 11.0, y: contentContainerFrame.minY), size: contentContainerFrame.size)
|
||||
}
|
||||
}
|
||||
|
||||
let scrollContentSize = CGSize(width: layout.size.width, height: contentHeight)
|
||||
@ -1692,18 +1732,22 @@ public protocol ContextReferenceContentSource: class {
|
||||
public final class ContextControllerTakeViewInfo {
|
||||
public let contentContainingNode: ContextExtractedContentContainingNode
|
||||
public let contentAreaInScreenSpace: CGRect
|
||||
public let maskView: UIView?
|
||||
|
||||
public init(contentContainingNode: ContextExtractedContentContainingNode, contentAreaInScreenSpace: CGRect) {
|
||||
public init(contentContainingNode: ContextExtractedContentContainingNode, contentAreaInScreenSpace: CGRect, maskView: UIView? = nil) {
|
||||
self.contentContainingNode = contentContainingNode
|
||||
self.contentAreaInScreenSpace = contentAreaInScreenSpace
|
||||
self.maskView = maskView
|
||||
}
|
||||
}
|
||||
|
||||
public final class ContextControllerPutBackViewInfo {
|
||||
public let contentAreaInScreenSpace: CGRect
|
||||
public let maskView: UIView?
|
||||
|
||||
public init(contentAreaInScreenSpace: CGRect) {
|
||||
public init(contentAreaInScreenSpace: CGRect, maskView: UIView? = nil) {
|
||||
self.contentAreaInScreenSpace = contentAreaInScreenSpace
|
||||
self.maskView = maskView
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,6 +41,10 @@ public final class PeekController: ViewController, ContextControllerProtocol {
|
||||
return self.displayNode as! PeekControllerNode
|
||||
}
|
||||
|
||||
public var contentNode: PeekControllerContentNode & ASDisplayNode {
|
||||
return self.controllerNode.contentNode
|
||||
}
|
||||
|
||||
private let presentationData: PresentationData
|
||||
private let content: PeekControllerContent
|
||||
var sourceNode: () -> ASDisplayNode?
|
||||
|
@ -23,7 +23,7 @@ final class PeekControllerNode: ViewControllerTracingNode {
|
||||
private var validLayout: ContainerViewLayout?
|
||||
|
||||
private var content: PeekControllerContent
|
||||
private var contentNode: PeekControllerContentNode & ASDisplayNode
|
||||
var contentNode: PeekControllerContentNode & ASDisplayNode
|
||||
private var contentNodeHasValidLayout = false
|
||||
|
||||
private var topAccessoryNode: ASDisplayNode?
|
||||
@ -274,6 +274,9 @@ final class PeekControllerNode: ViewControllerTracingNode {
|
||||
}
|
||||
|
||||
func activateMenu() {
|
||||
if self.content.menuItems().isEmpty {
|
||||
return
|
||||
}
|
||||
if case .press = self.content.menuActivation() {
|
||||
self.hapticFeedback.impact()
|
||||
}
|
||||
@ -307,6 +310,8 @@ final class PeekControllerNode: ViewControllerTracingNode {
|
||||
self.highlightedActionNode = nil
|
||||
highlightedActionNode.performAction()
|
||||
}
|
||||
} else if self.actionsContainerNode.alpha.isZero {
|
||||
self.requestDismiss()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -339,7 +339,7 @@ public final class AuthorizationSequenceCountrySelectionController: ViewControll
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition)
|
||||
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
|
||||
}
|
||||
|
||||
private func cancelPressed() {
|
||||
|
@ -19,6 +19,7 @@ swift_library(
|
||||
"//submodules/PresentationDataUtils:PresentationDataUtils",
|
||||
"//submodules/OverlayStatusController:OverlayStatusController",
|
||||
"//submodules/AccountContext:AccountContext",
|
||||
"//submodules/AppBundle:AppBundle",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -13,6 +13,7 @@ import ItemListUI
|
||||
import PresentationDataUtils
|
||||
import OverlayStatusController
|
||||
import AccountContext
|
||||
import AppBundle
|
||||
|
||||
@objc private final class DebugControllerMailComposeDelegate: NSObject, MFMailComposeViewControllerDelegate {
|
||||
public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
|
||||
@ -27,18 +28,21 @@ private final class DebugControllerArguments {
|
||||
let presentController: (ViewController, ViewControllerPresentationArguments?) -> Void
|
||||
let pushController: (ViewController) -> Void
|
||||
let getRootController: () -> UIViewController?
|
||||
let getNavigationController: () -> NavigationController?
|
||||
|
||||
init(sharedContext: SharedAccountContext, context: AccountContext?, mailComposeDelegate: DebugControllerMailComposeDelegate, presentController: @escaping (ViewController, ViewControllerPresentationArguments?) -> Void, pushController: @escaping (ViewController) -> Void, getRootController: @escaping () -> UIViewController?) {
|
||||
init(sharedContext: SharedAccountContext, context: AccountContext?, mailComposeDelegate: DebugControllerMailComposeDelegate, presentController: @escaping (ViewController, ViewControllerPresentationArguments?) -> Void, pushController: @escaping (ViewController) -> Void, getRootController: @escaping () -> UIViewController?, getNavigationController: @escaping () -> NavigationController?) {
|
||||
self.sharedContext = sharedContext
|
||||
self.context = context
|
||||
self.mailComposeDelegate = mailComposeDelegate
|
||||
self.presentController = presentController
|
||||
self.pushController = pushController
|
||||
self.getRootController = getRootController
|
||||
self.getNavigationController = getNavigationController
|
||||
}
|
||||
}
|
||||
|
||||
private enum DebugControllerSection: Int32 {
|
||||
case sticker
|
||||
case logs
|
||||
case logging
|
||||
case experiments
|
||||
@ -48,6 +52,7 @@ private enum DebugControllerSection: Int32 {
|
||||
}
|
||||
|
||||
private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
case testStickerImport(PresentationTheme)
|
||||
case sendLogs(PresentationTheme)
|
||||
case sendOneLog(PresentationTheme)
|
||||
case sendShareLogs
|
||||
@ -86,6 +91,8 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
|
||||
var section: ItemListSectionId {
|
||||
switch self {
|
||||
case .testStickerImport:
|
||||
return DebugControllerSection.sticker.rawValue
|
||||
case .sendLogs, .sendOneLog, .sendShareLogs, .sendNotificationLogs, .sendCriticalLogs:
|
||||
return DebugControllerSection.logs.rawValue
|
||||
case .accounts:
|
||||
@ -107,68 +114,70 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
|
||||
var stableId: Int {
|
||||
switch self {
|
||||
case .sendLogs:
|
||||
case .testStickerImport:
|
||||
return 0
|
||||
case .sendOneLog:
|
||||
case .sendLogs:
|
||||
return 1
|
||||
case .sendShareLogs:
|
||||
case .sendOneLog:
|
||||
return 2
|
||||
case .sendNotificationLogs:
|
||||
case .sendShareLogs:
|
||||
return 3
|
||||
case .sendCriticalLogs:
|
||||
case .sendNotificationLogs:
|
||||
return 4
|
||||
case .accounts:
|
||||
case .sendCriticalLogs:
|
||||
return 5
|
||||
case .logToFile:
|
||||
case .accounts:
|
||||
return 6
|
||||
case .logToConsole:
|
||||
case .logToFile:
|
||||
return 7
|
||||
case .redactSensitiveData:
|
||||
case .logToConsole:
|
||||
return 8
|
||||
case .enableRaiseToSpeak:
|
||||
case .redactSensitiveData:
|
||||
return 9
|
||||
case .keepChatNavigationStack:
|
||||
case .enableRaiseToSpeak:
|
||||
return 10
|
||||
case .skipReadHistory:
|
||||
case .keepChatNavigationStack:
|
||||
return 11
|
||||
case .crashOnSlowQueries:
|
||||
case .skipReadHistory:
|
||||
return 12
|
||||
case .clearTips:
|
||||
case .crashOnSlowQueries:
|
||||
return 13
|
||||
case .crash:
|
||||
case .clearTips:
|
||||
return 14
|
||||
case .resetData:
|
||||
case .crash:
|
||||
return 15
|
||||
case .resetDatabase:
|
||||
case .resetData:
|
||||
return 16
|
||||
case .resetDatabaseAndCache:
|
||||
case .resetDatabase:
|
||||
return 17
|
||||
case .resetHoles:
|
||||
case .resetDatabaseAndCache:
|
||||
return 18
|
||||
case .reindexUnread:
|
||||
case .resetHoles:
|
||||
return 19
|
||||
case .resetBiometricsData:
|
||||
case .reindexUnread:
|
||||
return 20
|
||||
case .optimizeDatabase:
|
||||
case .resetBiometricsData:
|
||||
return 21
|
||||
case .photoPreview:
|
||||
case .optimizeDatabase:
|
||||
return 22
|
||||
case .knockoutWallpaper:
|
||||
case .photoPreview:
|
||||
return 23
|
||||
case .demoVideoChats:
|
||||
case .knockoutWallpaper:
|
||||
return 24
|
||||
case .experimentalCompatibility:
|
||||
case .demoVideoChats:
|
||||
return 25
|
||||
case .enableNoiseSuppression:
|
||||
case .experimentalCompatibility:
|
||||
return 26
|
||||
case .playerEmbedding:
|
||||
case .enableNoiseSuppression:
|
||||
return 27
|
||||
case .playlistPlayback:
|
||||
case .playerEmbedding:
|
||||
return 28
|
||||
case .voiceConference:
|
||||
case .playlistPlayback:
|
||||
return 29
|
||||
case .voiceConference:
|
||||
return 30
|
||||
case let .preferredVideoCodec(index, _, _, _):
|
||||
return 30 + index
|
||||
return 31 + index
|
||||
case .disableVideoAspectScaling:
|
||||
return 100
|
||||
case .enableVoipTcp:
|
||||
@ -187,7 +196,22 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem {
|
||||
let arguments = arguments as! DebugControllerArguments
|
||||
switch self {
|
||||
case let .sendLogs(theme):
|
||||
case .testStickerImport:
|
||||
return ItemListActionItem(presentationData: presentationData, title: "Simulate Stickers Import", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
guard let context = arguments.context else {
|
||||
return
|
||||
}
|
||||
if let url = getAppBundle().url(forResource: "importstickers", withExtension: "json"), let data = try? Data(contentsOf: url) {
|
||||
let dataType = "org.telegram.third-party.stickerset"
|
||||
if #available(iOS 10.0, *) {
|
||||
UIPasteboard.general.setItems([[dataType: data]], options: [UIPasteboard.OptionsKey.localOnly: true, UIPasteboard.OptionsKey.expirationDate: NSDate(timeIntervalSinceNow: 60)])
|
||||
} else {
|
||||
UIPasteboard.general.setData(data, forPasteboardType: dataType)
|
||||
}
|
||||
context.sharedContext.openResolvedUrl(.importStickers, context: context, urlContext: .generic, navigationController: arguments.getNavigationController(), openPeer: { _, _ in }, sendFile: nil, sendSticker: nil, requestMessageActionUrlAuth: nil, joinVoiceChat: nil, present: { c, a in arguments.presentController(c, a as? ViewControllerPresentationArguments) }, dismissInput: {}, contentContext: nil)
|
||||
}
|
||||
})
|
||||
case .sendLogs:
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: "Send Logs (Up to 40 MB)", label: "", sectionId: self.section, style: .blocks, action: {
|
||||
let _ = (Logger.shared.collectLogs()
|
||||
|> deliverOnMainQueue).start(next: { logs in
|
||||
@ -257,7 +281,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
arguments.presentController(actionSheet, nil)
|
||||
})
|
||||
})
|
||||
case let .sendOneLog(theme):
|
||||
case .sendOneLog:
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: "Send Latest Logs (Up to 4 MB)", label: "", sectionId: self.section, style: .blocks, action: {
|
||||
let _ = (Logger.shared.collectLogs()
|
||||
|> deliverOnMainQueue).start(next: { logs in
|
||||
@ -339,7 +363,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
arguments.presentController(actionSheet, nil)
|
||||
})
|
||||
})
|
||||
case let .sendShareLogs:
|
||||
case .sendShareLogs:
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: "Send Share Logs (Up to 40 MB)", label: "", sectionId: self.section, style: .blocks, action: {
|
||||
let _ = (Logger.shared.collectLogs(prefix: "/share-logs")
|
||||
|> deliverOnMainQueue).start(next: { logs in
|
||||
@ -409,7 +433,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
arguments.presentController(actionSheet, nil)
|
||||
})
|
||||
})
|
||||
case let .sendNotificationLogs(theme):
|
||||
case .sendNotificationLogs:
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: "Send Notification Logs", label: "", sectionId: self.section, style: .blocks, action: {
|
||||
let _ = (Logger(rootPath: arguments.sharedContext.basePath, basePath: arguments.sharedContext.basePath + "/notificationServiceLogs").collectLogs()
|
||||
|> deliverOnMainQueue).start(next: { logs in
|
||||
@ -434,7 +458,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
arguments.pushController(controller)
|
||||
})
|
||||
})
|
||||
case let .sendCriticalLogs(theme):
|
||||
case .sendCriticalLogs:
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: "Send Critical Logs", label: "", sectionId: self.section, style: .blocks, action: {
|
||||
let _ = (Logger.shared.collectShortLogFiles()
|
||||
|> deliverOnMainQueue).start(next: { logs in
|
||||
@ -487,38 +511,38 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
arguments.presentController(actionSheet, nil)
|
||||
})
|
||||
})
|
||||
case let .accounts(theme):
|
||||
case .accounts:
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: "Accounts", label: "", sectionId: self.section, style: .blocks, action: {
|
||||
guard let context = arguments.context else {
|
||||
return
|
||||
}
|
||||
arguments.pushController(debugAccountsController(context: context, accountManager: arguments.sharedContext.accountManager))
|
||||
})
|
||||
case let .logToFile(theme, value):
|
||||
case let .logToFile(_, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: "Log to File", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
let _ = updateLoggingSettings(accountManager: arguments.sharedContext.accountManager, {
|
||||
$0.withUpdatedLogToFile(value)
|
||||
}).start()
|
||||
})
|
||||
case let .logToConsole(theme, value):
|
||||
case let .logToConsole(_, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: "Log to Console", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
let _ = updateLoggingSettings(accountManager: arguments.sharedContext.accountManager, {
|
||||
$0.withUpdatedLogToConsole(value)
|
||||
}).start()
|
||||
})
|
||||
case let .redactSensitiveData(theme, value):
|
||||
case let .redactSensitiveData(_, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: "Remove Sensitive Data", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
let _ = updateLoggingSettings(accountManager: arguments.sharedContext.accountManager, {
|
||||
$0.withUpdatedRedactSensitiveData(value)
|
||||
}).start()
|
||||
})
|
||||
case let .enableRaiseToSpeak(theme, value):
|
||||
case let .enableRaiseToSpeak(_, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: "Enable Raise to Speak", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
let _ = updateMediaInputSettingsInteractively(accountManager: arguments.sharedContext.accountManager, {
|
||||
$0.withUpdatedEnableRaiseToSpeak(value)
|
||||
}).start()
|
||||
})
|
||||
case let .keepChatNavigationStack(theme, value):
|
||||
case let .keepChatNavigationStack(_, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: "Keep Chat Stack", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in
|
||||
var settings = settings
|
||||
@ -526,7 +550,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
return settings
|
||||
}).start()
|
||||
})
|
||||
case let .skipReadHistory(theme, value):
|
||||
case let .skipReadHistory(_, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: "Skip read history", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in
|
||||
var settings = settings
|
||||
@ -534,7 +558,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
return settings
|
||||
}).start()
|
||||
})
|
||||
case let .crashOnSlowQueries(theme, value):
|
||||
case let .crashOnSlowQueries(_, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: "Crash when slow", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
let _ = updateExperimentalUISettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in
|
||||
var settings = settings
|
||||
@ -542,7 +566,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
return settings
|
||||
}).start()
|
||||
})
|
||||
case let .clearTips(theme):
|
||||
case .clearTips:
|
||||
return ItemListActionItem(presentationData: presentationData, title: "Clear Tips", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
let _ = (arguments.sharedContext.accountManager.transaction { transaction -> Void in
|
||||
transaction.clearNotices()
|
||||
@ -556,11 +580,11 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
}).start()
|
||||
}
|
||||
})
|
||||
case let .crash(theme):
|
||||
case .crash:
|
||||
return ItemListActionItem(presentationData: presentationData, title: "Crash", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
preconditionFailure()
|
||||
})
|
||||
case let .resetData(theme):
|
||||
case .resetData:
|
||||
return ItemListActionItem(presentationData: presentationData, title: "Reset Data", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
let presentationData = arguments.sharedContext.currentPresentationData.with { $0 }
|
||||
let actionSheet = ActionSheetController(presentationData: presentationData)
|
||||
@ -579,7 +603,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
])])
|
||||
arguments.presentController(actionSheet, nil)
|
||||
})
|
||||
case let .resetDatabase(theme):
|
||||
case .resetDatabase:
|
||||
return ItemListActionItem(presentationData: presentationData, title: "Clear Database", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
guard let context = arguments.context else {
|
||||
return
|
||||
@ -602,7 +626,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
])])
|
||||
arguments.presentController(actionSheet, nil)
|
||||
})
|
||||
case let .resetDatabaseAndCache(theme):
|
||||
case .resetDatabaseAndCache:
|
||||
return ItemListActionItem(presentationData: presentationData, title: "Clear Database and Cache", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
guard let context = arguments.context else {
|
||||
return
|
||||
@ -625,7 +649,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
])])
|
||||
arguments.presentController(actionSheet, nil)
|
||||
})
|
||||
case let .resetHoles(theme):
|
||||
case .resetHoles:
|
||||
return ItemListActionItem(presentationData: presentationData, title: "Reset Holes", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
guard let context = arguments.context else {
|
||||
return
|
||||
@ -640,7 +664,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
controller.dismiss()
|
||||
})
|
||||
})
|
||||
case let .reindexUnread(theme):
|
||||
case .reindexUnread:
|
||||
return ItemListActionItem(presentationData: presentationData, title: "Reindex Unread Counters", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
guard let context = arguments.context else {
|
||||
return
|
||||
@ -655,13 +679,13 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
controller.dismiss()
|
||||
})
|
||||
})
|
||||
case let .resetBiometricsData(theme):
|
||||
case .resetBiometricsData:
|
||||
return ItemListActionItem(presentationData: presentationData, title: "Reset Biometrics Data", kind: .destructive, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
let _ = updatePresentationPasscodeSettingsInteractively(accountManager: arguments.sharedContext.accountManager, { settings in
|
||||
return settings.withUpdatedBiometricsDomainState(nil).withUpdatedShareBiometricsDomainState(nil)
|
||||
}).start()
|
||||
})
|
||||
case let .optimizeDatabase(theme):
|
||||
case .optimizeDatabase:
|
||||
return ItemListActionItem(presentationData: presentationData, title: "Optimize Database", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||
guard let context = arguments.context else {
|
||||
return
|
||||
@ -677,7 +701,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
arguments.presentController(controller, nil)
|
||||
})
|
||||
})
|
||||
case let .photoPreview(theme, value):
|
||||
case let .photoPreview(_, value):
|
||||
return ItemListSwitchItem(presentationData: presentationData, title: "Media Preview (Updated)", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
|
||||
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
|
||||
@ -783,9 +807,9 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
})
|
||||
}).start()
|
||||
})
|
||||
case let .hostInfo(theme, string):
|
||||
case let .hostInfo(_, string):
|
||||
return ItemListTextItem(presentationData: presentationData, text: .plain(string), sectionId: self.section)
|
||||
case let .versionInfo(theme):
|
||||
case .versionInfo:
|
||||
let bundle = Bundle.main
|
||||
let bundleId = bundle.bundleIdentifier ?? ""
|
||||
let bundleVersion = bundle.infoDictionary?["CFBundleShortVersionString"] ?? ""
|
||||
@ -800,6 +824,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
|
||||
|
||||
let isMainApp = sharedContext.applicationBindings.isMainApp
|
||||
|
||||
// entries.append(.testStickerImport(presentationData.theme))
|
||||
entries.append(.sendLogs(presentationData.theme))
|
||||
entries.append(.sendOneLog(presentationData.theme))
|
||||
entries.append(.sendShareLogs)
|
||||
@ -872,6 +897,7 @@ public func debugController(sharedContext: SharedAccountContext, context: Accoun
|
||||
var pushControllerImpl: ((ViewController) -> Void)?
|
||||
var dismissImpl: (() -> Void)?
|
||||
var getRootControllerImpl: (() -> UIViewController?)?
|
||||
var getNavigationControllerImpl: (() -> NavigationController?)?
|
||||
|
||||
let arguments = DebugControllerArguments(sharedContext: sharedContext, context: context, mailComposeDelegate: DebugControllerMailComposeDelegate(), presentController: { controller, arguments in
|
||||
presentControllerImpl?(controller, arguments)
|
||||
@ -879,6 +905,8 @@ public func debugController(sharedContext: SharedAccountContext, context: Accoun
|
||||
pushControllerImpl?(controller)
|
||||
}, getRootController: {
|
||||
return getRootControllerImpl?()
|
||||
}, getNavigationController: {
|
||||
return getNavigationControllerImpl?()
|
||||
})
|
||||
|
||||
let appGroupName = "group.\(Bundle.main.bundleIdentifier!)"
|
||||
@ -945,5 +973,8 @@ public func debugController(sharedContext: SharedAccountContext, context: Accoun
|
||||
getRootControllerImpl = { [weak controller] in
|
||||
return controller?.view.window?.rootViewController
|
||||
}
|
||||
getNavigationControllerImpl = { [weak controller] in
|
||||
return controller?.navigationController as? NavigationController
|
||||
}
|
||||
return controller
|
||||
}
|
||||
|
@ -87,6 +87,10 @@ public final class DeviceAccess {
|
||||
return AVAudioSession.sharedInstance().recordPermission == .granted
|
||||
}
|
||||
|
||||
public static func isCameraAccessAuthorized() -> Bool {
|
||||
return PGCamera.cameraAuthorizationStatus() == PGCameraAuthorizationStatusAuthorized
|
||||
}
|
||||
|
||||
public static func authorizationStatus(applicationInForeground: Signal<Bool, NoError>? = nil, siriAuthorization: (() -> AccessType)? = nil, subject: DeviceAccessSubject) -> Signal<AccessType, NoError> {
|
||||
switch subject {
|
||||
case .notifications:
|
||||
@ -250,27 +254,31 @@ public final class DeviceAccess {
|
||||
}
|
||||
}
|
||||
|
||||
public static func authorizeAccess(to subject: DeviceAccessSubject, registerForNotifications: ((@escaping (Bool) -> Void) -> Void)? = nil, requestSiriAuthorization: ((@escaping (Bool) -> Void) -> Void)? = nil, locationManager: LocationManager? = nil, presentationData: PresentationData? = nil, present: @escaping (ViewController, Any?) -> Void = { _, _ in }, openSettings: @escaping () -> Void = { }, displayNotificationFromBackground: @escaping (String) -> Void = { _ in }, _ completion: @escaping (Bool) -> Void = { _ in }) {
|
||||
public static func authorizeAccess(to subject: DeviceAccessSubject, onlyCheck: Bool = false, registerForNotifications: ((@escaping (Bool) -> Void) -> Void)? = nil, requestSiriAuthorization: ((@escaping (Bool) -> Void) -> Void)? = nil, locationManager: LocationManager? = nil, presentationData: PresentationData? = nil, present: @escaping (ViewController, Any?) -> Void = { _, _ in }, openSettings: @escaping () -> Void = { }, displayNotificationFromBackground: @escaping (String) -> Void = { _ in }, _ completion: @escaping (Bool) -> Void = { _ in }) {
|
||||
switch subject {
|
||||
case let .camera(cameraSubject):
|
||||
let status = PGCamera.cameraAuthorizationStatus()
|
||||
if status == PGCameraAuthorizationStatusNotDetermined {
|
||||
AVCaptureDevice.requestAccess(for: AVMediaType.video) { response in
|
||||
Queue.mainQueue().async {
|
||||
completion(response)
|
||||
if !response, let presentationData = presentationData {
|
||||
let text: String
|
||||
switch cameraSubject {
|
||||
case .video:
|
||||
text = presentationData.strings.AccessDenied_Camera
|
||||
case .videoCall:
|
||||
text = presentationData.strings.AccessDenied_VideoCallCamera
|
||||
if !onlyCheck {
|
||||
AVCaptureDevice.requestAccess(for: AVMediaType.video) { response in
|
||||
Queue.mainQueue().async {
|
||||
completion(response)
|
||||
if !response, let presentationData = presentationData {
|
||||
let text: String
|
||||
switch cameraSubject {
|
||||
case .video:
|
||||
text = presentationData.strings.AccessDenied_Camera
|
||||
case .videoCall:
|
||||
text = presentationData.strings.AccessDenied_VideoCallCamera
|
||||
}
|
||||
present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: {
|
||||
openSettings()
|
||||
})]), nil)
|
||||
}
|
||||
present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: presentationData.strings.AccessDenied_Title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_NotNow, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.AccessDenied_Settings, action: {
|
||||
openSettings()
|
||||
})]), nil)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
completion(true)
|
||||
}
|
||||
} else if status == PGCameraAuthorizationStatusRestricted || status == PGCameraAuthorizationStatusDenied, let presentationData = presentationData {
|
||||
let text: String
|
||||
|
@ -86,11 +86,15 @@ open class AlertController: ViewController, StandalonePresentableController {
|
||||
private let contentNode: AlertContentNode
|
||||
private let allowInputInset: Bool
|
||||
|
||||
private weak var existingAlertController: AlertController?
|
||||
|
||||
public var willDismiss: (() -> Void)?
|
||||
public var dismissed: (() -> Void)?
|
||||
|
||||
public init(theme: AlertControllerTheme, contentNode: AlertContentNode, allowInputInset: Bool = true) {
|
||||
public init(theme: AlertControllerTheme, contentNode: AlertContentNode, existingAlertController: AlertController? = nil, allowInputInset: Bool = true) {
|
||||
self.theme = theme
|
||||
self.contentNode = contentNode
|
||||
self.existingAlertController = existingAlertController
|
||||
self.allowInputInset = allowInputInset
|
||||
|
||||
super.init(navigationBarPresentationData: nil)
|
||||
@ -108,8 +112,11 @@ open class AlertController: ViewController, StandalonePresentableController {
|
||||
self.displayNode = AlertControllerNode(contentNode: self.contentNode, theme: self.theme, allowInputInset: self.allowInputInset)
|
||||
self.displayNodeDidLoad()
|
||||
|
||||
self.controllerNode.existingAlertControllerNode = self.existingAlertController?.controllerNode
|
||||
|
||||
self.controllerNode.dismiss = { [weak self] in
|
||||
if let strongSelf = self, strongSelf.contentNode.dismissOnOutsideTap {
|
||||
strongSelf.willDismiss?()
|
||||
strongSelf.controllerNode.animateOut {
|
||||
self?.dismiss()
|
||||
}
|
||||
@ -120,6 +127,9 @@ open class AlertController: ViewController, StandalonePresentableController {
|
||||
override open func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
self.existingAlertController?.dismiss(completion: nil)
|
||||
self.existingAlertController = nil
|
||||
|
||||
self.controllerNode.animateIn()
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,8 @@ import UIKit
|
||||
import AsyncDisplayKit
|
||||
|
||||
final class AlertControllerNode: ASDisplayNode {
|
||||
var existingAlertControllerNode: AlertControllerNode?
|
||||
|
||||
private let centerDimView: UIImageView
|
||||
private let topDimView: UIView
|
||||
private let bottomDimView: UIView
|
||||
@ -90,18 +92,35 @@ final class AlertControllerNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
func animateIn() {
|
||||
self.centerDimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
self.topDimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
self.bottomDimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
self.leftDimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
self.rightDimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, completion: { [weak self] finished in
|
||||
if finished {
|
||||
self?.centerDimView.backgroundColor = nil
|
||||
self?.centerDimView.image = generateStretchableFilledCircleImage(radius: 16.0, color: nil, backgroundColor: UIColor(white: 0.0, alpha: 0.5))
|
||||
if let previousNode = self.existingAlertControllerNode {
|
||||
let transition = ContainedViewLayoutTransition.animated(duration: 0.3, curve: .spring)
|
||||
|
||||
previousNode.position = previousNode.position.offsetBy(dx: -previousNode.frame.width, dy: 0.0)
|
||||
self.addSubnode(previousNode)
|
||||
|
||||
let position = self.position
|
||||
self.position = position.offsetBy(dx: self.frame.width, dy: 0.0)
|
||||
transition.animateView {
|
||||
self.position = position
|
||||
} completion: { _ in
|
||||
previousNode.removeFromSupernode()
|
||||
}
|
||||
})
|
||||
self.containerNode.layer.animateSpring(from: 0.8 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5, initialVelocity: 0.0, removeOnCompletion: true, additive: false, completion: nil)
|
||||
|
||||
self.existingAlertControllerNode = nil
|
||||
} else {
|
||||
self.centerDimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
self.topDimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
self.bottomDimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
self.leftDimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
self.rightDimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, completion: { [weak self] finished in
|
||||
if finished {
|
||||
self?.centerDimView.backgroundColor = nil
|
||||
self?.centerDimView.image = generateStretchableFilledCircleImage(radius: 16.0, color: nil, backgroundColor: UIColor(white: 0.0, alpha: 0.5))
|
||||
}
|
||||
})
|
||||
self.containerNode.layer.animateSpring(from: 0.8 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5, initialVelocity: 0.0, removeOnCompletion: true, additive: false, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
func animateOut(completion: @escaping () -> Void) {
|
||||
|
@ -31,6 +31,7 @@ import UIKitRuntimeUtils
|
||||
private let completionKey = "CAAnimationUtils_completion"
|
||||
|
||||
public let kCAMediaTimingFunctionSpring = "CAAnimationUtilsSpringCurve"
|
||||
public let kCAMediaTimingFunctionCustomSpringPrefix = "CAAnimationUtilsSpringCustomCurve"
|
||||
|
||||
public extension CAAnimation {
|
||||
var completion: ((Bool) -> Void)? {
|
||||
@ -52,7 +53,38 @@ public extension CAAnimation {
|
||||
|
||||
public extension CALayer {
|
||||
func makeAnimation(from: AnyObject, to: AnyObject, keyPath: String, timingFunction: String, duration: Double, delay: Double = 0.0, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) -> CAAnimation {
|
||||
if timingFunction == kCAMediaTimingFunctionSpring {
|
||||
if timingFunction.hasPrefix(kCAMediaTimingFunctionCustomSpringPrefix) {
|
||||
let components = timingFunction.components(separatedBy: "_")
|
||||
let damping = Float(components[1]) ?? 100.0
|
||||
let initialVelocity = Float(components[2]) ?? 0.0
|
||||
|
||||
let animation = CASpringAnimation(keyPath: keyPath)
|
||||
animation.fromValue = from
|
||||
animation.toValue = to
|
||||
animation.isRemovedOnCompletion = removeOnCompletion
|
||||
animation.fillMode = .forwards
|
||||
if let completion = completion {
|
||||
animation.delegate = CALayerAnimationDelegate(animation: animation, completion: completion)
|
||||
}
|
||||
animation.damping = CGFloat(damping)
|
||||
animation.initialVelocity = CGFloat(initialVelocity)
|
||||
animation.mass = 5.0
|
||||
animation.stiffness = 900.0
|
||||
animation.duration = animation.settlingDuration
|
||||
animation.timingFunction = CAMediaTimingFunction.init(name: .linear)
|
||||
let k = Float(UIView.animationDurationFactor())
|
||||
var speed: Float = 1.0
|
||||
if k != 0 && k != 1 {
|
||||
speed = Float(1.0) / k
|
||||
}
|
||||
animation.speed = speed * Float(animation.duration / duration)
|
||||
animation.isAdditive = additive
|
||||
if !delay.isZero {
|
||||
animation.beginTime = self.convertTime(CACurrentMediaTime(), from: nil) + delay * UIView.animationDurationFactor()
|
||||
animation.fillMode = .both
|
||||
}
|
||||
return animation
|
||||
} else if timingFunction == kCAMediaTimingFunctionSpring {
|
||||
let animation = makeSpringAnimation(keyPath)
|
||||
animation.fromValue = from
|
||||
animation.toValue = to
|
||||
@ -261,6 +293,26 @@ public extension CALayer {
|
||||
}
|
||||
self.animate(from: NSValue(cgRect: from), to: NSValue(cgRect: to), keyPath: "bounds", timingFunction: timingFunction, duration: duration, delay: delay, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion)
|
||||
}
|
||||
|
||||
func animateWidth(from: CGFloat, to: CGFloat, duration: Double, delay: Double = 0.0, timingFunction: String, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, additive: Bool = false, force: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||
if from == to && !force {
|
||||
if let completion = completion {
|
||||
completion(true)
|
||||
}
|
||||
return
|
||||
}
|
||||
self.animate(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.size.width", timingFunction: timingFunction, duration: duration, delay: delay, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion)
|
||||
}
|
||||
|
||||
func animateHeight(from: CGFloat, to: CGFloat, duration: Double, delay: Double = 0.0, timingFunction: String, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, additive: Bool = false, force: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||
if from == to && !force {
|
||||
if let completion = completion {
|
||||
completion(true)
|
||||
}
|
||||
return
|
||||
}
|
||||
self.animate(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.size.height", timingFunction: timingFunction, duration: duration, delay: delay, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion)
|
||||
}
|
||||
|
||||
func animateBoundsOriginXAdditive(from: CGFloat, to: CGFloat, duration: Double, timingFunction: String = CAMediaTimingFunctionName.easeInEaseOut.rawValue, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) {
|
||||
self.animate(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.x", timingFunction: timingFunction, duration: duration, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: completion)
|
||||
|
@ -6,6 +6,7 @@ public enum ContainedViewLayoutTransitionCurve: Equatable, Hashable {
|
||||
case linear
|
||||
case easeInOut
|
||||
case spring
|
||||
case customSpring(damping: CGFloat, initialVelocity: CGFloat)
|
||||
case custom(Float, Float, Float, Float)
|
||||
|
||||
public static var slide: ContainedViewLayoutTransitionCurve {
|
||||
@ -22,6 +23,8 @@ public extension ContainedViewLayoutTransitionCurve {
|
||||
return listViewAnimationCurveEaseInOut(offset)
|
||||
case .spring:
|
||||
return listViewAnimationCurveSystem(offset)
|
||||
case .customSpring:
|
||||
return listViewAnimationCurveSystem(offset)
|
||||
case let .custom(c1x, c1y, c2x, c2y):
|
||||
return bezierPoint(CGFloat(c1x), CGFloat(c1y), CGFloat(c2x), CGFloat(c2y), offset)
|
||||
}
|
||||
@ -37,6 +40,8 @@ public extension ContainedViewLayoutTransitionCurve {
|
||||
return CAMediaTimingFunctionName.easeInEaseOut.rawValue
|
||||
case .spring:
|
||||
return kCAMediaTimingFunctionSpring
|
||||
case let .customSpring(damping, initialVelocity):
|
||||
return "\(kCAMediaTimingFunctionCustomSpringPrefix)_\(damping)_\(initialVelocity)"
|
||||
case .custom:
|
||||
return CAMediaTimingFunctionName.easeInEaseOut.rawValue
|
||||
}
|
||||
@ -50,6 +55,8 @@ public extension ContainedViewLayoutTransitionCurve {
|
||||
return nil
|
||||
case .spring:
|
||||
return nil
|
||||
case .customSpring:
|
||||
return nil
|
||||
case let .custom(p1, p2, p3, p4):
|
||||
return CAMediaTimingFunction(controlPoints: p1, p2, p3, p4)
|
||||
}
|
||||
@ -64,6 +71,8 @@ public extension ContainedViewLayoutTransitionCurve {
|
||||
return [.curveEaseInOut]
|
||||
case .spring:
|
||||
return UIView.AnimationOptions(rawValue: 7 << 16)
|
||||
case .customSpring:
|
||||
return UIView.AnimationOptions(rawValue: 7 << 16)
|
||||
case .custom:
|
||||
return []
|
||||
}
|
||||
@ -324,8 +333,8 @@ public extension ContainedViewLayoutTransition {
|
||||
}
|
||||
}
|
||||
|
||||
func updatePosition(layer: CALayer, position: CGPoint, completion: ((Bool) -> Void)? = nil) {
|
||||
if layer.position.equalTo(position) {
|
||||
func updatePosition(layer: CALayer, position: CGPoint, force: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||
if layer.position.equalTo(position) && !force {
|
||||
completion?(true)
|
||||
} else {
|
||||
switch self {
|
||||
@ -394,6 +403,21 @@ public extension ContainedViewLayoutTransition {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func animateFrame(layer: CALayer, from frame: CGRect, to toFrame: CGRect? = nil, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||
switch self {
|
||||
case .immediate:
|
||||
if let completion = completion {
|
||||
completion(true)
|
||||
}
|
||||
case let .animated(duration, curve):
|
||||
layer.animateFrame(from: frame, to: toFrame ?? layer.frame, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: { result in
|
||||
if let completion = completion {
|
||||
completion(result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func animateBounds(layer: CALayer, from bounds: CGRect, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) {
|
||||
switch self {
|
||||
@ -409,6 +433,36 @@ public extension ContainedViewLayoutTransition {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func animateWidthAdditive(layer: CALayer, value: CGFloat, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) {
|
||||
switch self {
|
||||
case .immediate:
|
||||
if let completion = completion {
|
||||
completion(true)
|
||||
}
|
||||
case let .animated(duration, curve):
|
||||
layer.animateWidth(from: value, to: 0.0, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: { result in
|
||||
if let completion = completion {
|
||||
completion(result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func animateHeightAdditive(layer: CALayer, value: CGFloat, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) {
|
||||
switch self {
|
||||
case .immediate:
|
||||
if let completion = completion {
|
||||
completion(true)
|
||||
}
|
||||
case let .animated(duration, curve):
|
||||
layer.animateHeight(from: value, to: 0.0, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: { result in
|
||||
if let completion = completion {
|
||||
completion(result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func animateOffsetAdditive(node: ASDisplayNode, offset: CGFloat) {
|
||||
switch self {
|
||||
@ -429,6 +483,17 @@ public extension ContainedViewLayoutTransition {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func animateHorizontalOffsetAdditive(layer: CALayer, offset: CGFloat, completion: (() -> Void)? = nil) {
|
||||
switch self {
|
||||
case .immediate:
|
||||
break
|
||||
case let .animated(duration, curve):
|
||||
layer.animateBoundsOriginXAdditive(from: offset, to: 0.0, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { _ in
|
||||
completion?()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func animateOffsetAdditive(layer: CALayer, offset: CGFloat, completion: (() -> Void)? = nil) {
|
||||
switch self {
|
||||
@ -470,16 +535,25 @@ public extension ContainedViewLayoutTransition {
|
||||
}
|
||||
}
|
||||
|
||||
func animatePositionAdditive(layer: CALayer, offset: CGPoint, to toOffset: CGPoint = CGPoint(), removeOnCompletion: Bool = true, completion: (() -> Void)? = nil) {
|
||||
func animatePositionAdditive(layer: CALayer, offset: CGPoint, to toOffset: CGPoint = CGPoint(), removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) {
|
||||
switch self {
|
||||
case .immediate:
|
||||
completion?()
|
||||
completion?(true)
|
||||
case let .animated(duration, curve):
|
||||
layer.animatePosition(from: offset, to: toOffset, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: { _ in
|
||||
completion?()
|
||||
layer.animatePosition(from: offset, to: toOffset, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: { result in
|
||||
completion?(result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func animateContentsRectPositionAdditive(layer: CALayer, offset: CGPoint, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) {
|
||||
switch self {
|
||||
case .immediate:
|
||||
completion?(true)
|
||||
case let .animated(duration, curve):
|
||||
layer.animate(from: NSValue(cgPoint: offset), to: NSValue(cgPoint: CGPoint()), keyPath: "contentsRect.origin", timingFunction: curve.timingFunction, duration: duration, delay: 0.0, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: completion)
|
||||
}
|
||||
}
|
||||
|
||||
func updateFrame(view: UIView, frame: CGRect, force: Bool = false, beginWithCurrentState: Bool = false, delay: Double = 0.0, completion: ((Bool) -> Void)? = nil) {
|
||||
if frame.origin.x.isNaN {
|
||||
@ -1177,7 +1251,119 @@ public extension ContainedViewLayoutTransition {
|
||||
}
|
||||
}
|
||||
|
||||
#if os(iOS)
|
||||
public struct CombinedTransition {
|
||||
public var horizontal: ContainedViewLayoutTransition
|
||||
public var vertical: ContainedViewLayoutTransition
|
||||
|
||||
public var isAnimated: Bool {
|
||||
return self.horizontal.isAnimated || self.vertical.isAnimated
|
||||
}
|
||||
|
||||
public init(horizontal: ContainedViewLayoutTransition, vertical: ContainedViewLayoutTransition) {
|
||||
self.horizontal = horizontal
|
||||
self.vertical = vertical
|
||||
}
|
||||
|
||||
public func animateFrame(layer: CALayer, from fromFrame: CGRect, completion: ((Bool) -> Void)? = nil) {
|
||||
//self.horizontal.animateFrame(layer: layer, from: fromFrame, completion: completion)
|
||||
//return;
|
||||
|
||||
let toFrame = layer.frame
|
||||
|
||||
enum Keys: CaseIterable {
|
||||
case positionX, positionY
|
||||
case sizeWidth, sizeHeight
|
||||
}
|
||||
var remainingKeys = Keys.allCases
|
||||
var completedValue = true
|
||||
let completeKey: (Keys, Bool) -> Void = { key, completed in
|
||||
remainingKeys.removeAll(where: { $0 == key })
|
||||
if !completed {
|
||||
completedValue = false
|
||||
}
|
||||
if remainingKeys.isEmpty {
|
||||
completion?(completedValue)
|
||||
}
|
||||
}
|
||||
|
||||
self.horizontal.animatePositionAdditive(layer: layer, offset: CGPoint(x: fromFrame.midX - toFrame.midX, y: 0.0), completion: { result in
|
||||
completeKey(.positionX, result)
|
||||
})
|
||||
self.vertical.animatePositionAdditive(layer: layer, offset: CGPoint(x: 0.0, y: fromFrame.midY - toFrame.midY), completion: { result in
|
||||
completeKey(.positionY, result)
|
||||
})
|
||||
|
||||
self.horizontal.animateWidthAdditive(layer: layer, value: fromFrame.width - toFrame.width, completion: { result in
|
||||
completeKey(.sizeWidth, result)
|
||||
})
|
||||
self.vertical.animateHeightAdditive(layer: layer, value: fromFrame.height - toFrame.height, completion: { result in
|
||||
completeKey(.sizeHeight, result)
|
||||
})
|
||||
}
|
||||
|
||||
public func updateFrame(layer: CALayer, frame: CGRect, completion: ((Bool) -> Void)? = nil) {
|
||||
let fromFrame = layer.frame
|
||||
layer.frame = frame
|
||||
self.animateFrame(layer: layer, from: fromFrame, completion: completion)
|
||||
}
|
||||
|
||||
public func updateFrame(node: ASDisplayNode, frame: CGRect, completion: ((Bool) -> Void)? = nil) {
|
||||
let fromFrame = node.frame
|
||||
node.frame = frame
|
||||
self.animateFrame(layer: node.layer, from: fromFrame, completion: completion)
|
||||
}
|
||||
|
||||
public func updatePosition(layer: CALayer, position: CGPoint, completion: ((Bool) -> Void)? = nil) {
|
||||
let fromPosition = layer.position
|
||||
layer.position = position
|
||||
|
||||
enum Keys: CaseIterable {
|
||||
case positionX, positionY
|
||||
}
|
||||
var remainingKeys = Keys.allCases
|
||||
var completedValue = true
|
||||
let completeKey: (Keys, Bool) -> Void = { key, completed in
|
||||
remainingKeys.removeAll(where: { $0 == key })
|
||||
if !completed {
|
||||
completedValue = false
|
||||
}
|
||||
if remainingKeys.isEmpty {
|
||||
completion?(completedValue)
|
||||
}
|
||||
}
|
||||
|
||||
self.horizontal.animatePositionAdditive(layer: layer, offset: CGPoint(x: fromPosition.x - position.x, y: 0.0), completion: { result in
|
||||
completeKey(.positionX, result)
|
||||
})
|
||||
self.vertical.animatePositionAdditive(layer: layer, offset: CGPoint(x: 0.0, y: fromPosition.y - position.y), completion: { result in
|
||||
completeKey(.positionY, result)
|
||||
})
|
||||
}
|
||||
|
||||
public func animatePositionAdditive(layer: CALayer, offset: CGPoint, to toOffset: CGPoint = CGPoint(), removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) {
|
||||
enum Keys: CaseIterable {
|
||||
case positionX, positionY
|
||||
}
|
||||
var remainingKeys = Keys.allCases
|
||||
var completedValue = true
|
||||
let completeKey: (Keys, Bool) -> Void = { key, completed in
|
||||
remainingKeys.removeAll(where: { $0 == key })
|
||||
if !completed {
|
||||
completedValue = false
|
||||
}
|
||||
if remainingKeys.isEmpty {
|
||||
completion?(completedValue)
|
||||
}
|
||||
}
|
||||
|
||||
self.horizontal.animatePositionAdditive(layer: layer, offset: CGPoint(x: offset.x, y: 0.0), to: CGPoint(x: toOffset.x, y: 0.0), completion: { result in
|
||||
completeKey(.positionX, result)
|
||||
})
|
||||
self.vertical.animatePositionAdditive(layer: layer, offset: CGPoint(x: 0.0, y: offset.y), to: CGPoint(x: 0.0, y: toOffset.y), completion: { result in
|
||||
completeKey(.positionY, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
public extension ContainedViewLayoutTransition {
|
||||
func animateView(_ f: @escaping () -> Void, completion: ((Bool) -> Void)? = nil) {
|
||||
@ -1192,5 +1378,3 @@ public extension ContainedViewLayoutTransition {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -58,7 +58,13 @@ public struct Font {
|
||||
|
||||
public static func with(size: CGFloat, design: Design = .regular, weight: Weight = .regular, traits: Traits = []) -> UIFont {
|
||||
if #available(iOS 13.0, *) {
|
||||
let descriptor = UIFont.systemFont(ofSize: size).fontDescriptor
|
||||
let descriptor: UIFontDescriptor
|
||||
if #available(iOS 14.0, *) {
|
||||
descriptor = UIFont.systemFont(ofSize: size).fontDescriptor
|
||||
} else {
|
||||
descriptor = UIFont.systemFont(ofSize: size, weight: weight.weight).fontDescriptor
|
||||
}
|
||||
|
||||
var symbolicTraits = descriptor.symbolicTraits
|
||||
if traits.contains(.italic) {
|
||||
symbolicTraits.insert(.traitItalic)
|
||||
@ -83,10 +89,12 @@ public struct Font {
|
||||
default:
|
||||
updatedDescriptor = updatedDescriptor?.withDesign(.default)
|
||||
}
|
||||
if weight != .regular {
|
||||
updatedDescriptor = updatedDescriptor?.addingAttributes([
|
||||
UIFontDescriptor.AttributeName.traits: [UIFontDescriptor.TraitKey.weight: weight.weight]
|
||||
])
|
||||
if #available(iOS 14.0, *) {
|
||||
if weight != .regular {
|
||||
updatedDescriptor = updatedDescriptor?.addingAttributes([
|
||||
UIFontDescriptor.AttributeName.traits: [UIFontDescriptor.TraitKey.weight: weight.weight]
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
if let updatedDescriptor = updatedDescriptor {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Accelerate
|
||||
import AsyncDisplayKit
|
||||
|
||||
public let deviceColorSpace: CGColorSpace = {
|
||||
if #available(iOSApplicationExtension 9.3, iOS 9.3, *) {
|
||||
@ -19,38 +20,20 @@ private let grayscaleColorSpace = CGColorSpaceCreateDeviceGray()
|
||||
let deviceScale = UIScreen.main.scale
|
||||
|
||||
public func generateImagePixel(_ size: CGSize, scale: CGFloat, pixelGenerator: (CGSize, UnsafeMutablePointer<UInt8>, Int) -> Void) -> UIImage? {
|
||||
let scaledSize = CGSize(width: size.width * scale, height: size.height * scale)
|
||||
let bytesPerRow = (4 * Int(scaledSize.width) + 15) & (~15)
|
||||
let length = bytesPerRow * Int(scaledSize.height)
|
||||
let bytes = malloc(length)!.assumingMemoryBound(to: UInt8.self)
|
||||
guard let provider = CGDataProvider(dataInfo: bytes, data: bytes, size: length, releaseData: { bytes, _, _ in
|
||||
free(bytes)
|
||||
})
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
pixelGenerator(scaledSize, bytes, bytesPerRow)
|
||||
|
||||
let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue)
|
||||
|
||||
guard let image = CGImage(width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: bitmapInfo, provider: provider, decode: nil, shouldInterpolate: false, intent: .defaultIntent)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return UIImage(cgImage: image, scale: scale, orientation: .up)
|
||||
let context = DrawingContext(size: size, scale: scale, opaque: false, clear: false)
|
||||
pixelGenerator(CGSize(width: size.width * scale, height: size.height * scale), context.bytes.assumingMemoryBound(to: UInt8.self), context.bytesPerRow)
|
||||
return context.generateImage()
|
||||
}
|
||||
|
||||
private func withImageBytes(image: UIImage, _ f: (UnsafePointer<UInt8>, Int, Int, Int) -> Void) {
|
||||
let selectedScale = image.scale
|
||||
let scaledSize = CGSize(width: image.size.width * selectedScale, height: image.size.height * selectedScale)
|
||||
let bytesPerRow = (4 * Int(scaledSize.width) + 15) & (~15)
|
||||
let bytesPerRow = DeviceGraphicsContextSettings.shared.bytesPerRow(forWidth: Int(scaledSize.width))
|
||||
let length = bytesPerRow * Int(scaledSize.height)
|
||||
let bytes = malloc(length)!.assumingMemoryBound(to: UInt8.self)
|
||||
memset(bytes, 0, length)
|
||||
|
||||
let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue)
|
||||
let bitmapInfo = DeviceGraphicsContextSettings.shared.transparentBitmapInfo
|
||||
|
||||
guard let context = CGContext(data: bytes, width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: bitmapInfo.rawValue) else {
|
||||
return
|
||||
@ -65,7 +48,7 @@ private func withImageBytes(image: UIImage, _ f: (UnsafePointer<UInt8>, Int, Int
|
||||
public func generateGrayscaleAlphaMaskImage(image: UIImage) -> UIImage? {
|
||||
let selectedScale = image.scale
|
||||
let scaledSize = CGSize(width: image.size.width * selectedScale, height: image.size.height * selectedScale)
|
||||
let bytesPerRow = (1 * Int(scaledSize.width) + 15) & (~15)
|
||||
let bytesPerRow = (1 * Int(scaledSize.width) + 31) & (~31)
|
||||
let length = bytesPerRow * Int(scaledSize.height)
|
||||
let bytes = malloc(length)!.assumingMemoryBound(to: UInt8.self)
|
||||
memset(bytes, 0, length)
|
||||
@ -111,69 +94,25 @@ public func generateGrayscaleAlphaMaskImage(image: UIImage) -> UIImage? {
|
||||
}
|
||||
|
||||
public func generateImage(_ size: CGSize, contextGenerator: (CGSize, CGContext) -> Void, opaque: Bool = false, scale: CGFloat? = nil) -> UIImage? {
|
||||
let selectedScale = scale ?? deviceScale
|
||||
let scaledSize = CGSize(width: size.width * selectedScale, height: size.height * selectedScale)
|
||||
let bytesPerRow = (4 * Int(scaledSize.width) + 15) & (~15)
|
||||
let length = bytesPerRow * Int(scaledSize.height)
|
||||
let bytes = malloc(length)!.assumingMemoryBound(to: Int8.self)
|
||||
|
||||
guard let provider = CGDataProvider(dataInfo: bytes, data: bytes, size: length, releaseData: { bytes, _, _ in
|
||||
free(bytes)
|
||||
})
|
||||
else {
|
||||
if size.width.isZero || size.height.isZero {
|
||||
return nil
|
||||
}
|
||||
|
||||
let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Little.rawValue | (opaque ? CGImageAlphaInfo.noneSkipFirst.rawValue : CGImageAlphaInfo.premultipliedFirst.rawValue))
|
||||
|
||||
guard let context = CGContext(data: bytes, width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: bitmapInfo.rawValue) else {
|
||||
return nil
|
||||
let context = DrawingContext(size: size, scale: scale ?? 0.0, opaque: opaque, clear: false)
|
||||
context.withFlippedContext { c in
|
||||
contextGenerator(context.size, c)
|
||||
}
|
||||
|
||||
context.scaleBy(x: selectedScale, y: selectedScale)
|
||||
|
||||
contextGenerator(size, context)
|
||||
|
||||
guard let image = CGImage(width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: bitmapInfo, provider: provider, decode: nil, shouldInterpolate: false, intent: .defaultIntent)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return UIImage(cgImage: image, scale: selectedScale, orientation: .up)
|
||||
return context.generateImage()
|
||||
}
|
||||
|
||||
public func generateImage(_ size: CGSize, opaque: Bool = false, scale: CGFloat? = nil, rotatedContext: (CGSize, CGContext) -> Void) -> UIImage? {
|
||||
let selectedScale = scale ?? deviceScale
|
||||
let scaledSize = CGSize(width: size.width * selectedScale, height: size.height * selectedScale)
|
||||
let bytesPerRow = (4 * Int(scaledSize.width) + 15) & (~15)
|
||||
let length = bytesPerRow * Int(scaledSize.height)
|
||||
let bytes = malloc(length)!.assumingMemoryBound(to: Int8.self)
|
||||
|
||||
guard let provider = CGDataProvider(dataInfo: bytes, data: bytes, size: length, releaseData: { bytes, _, _ in
|
||||
free(bytes)
|
||||
}) else {
|
||||
if size.width.isZero || size.height.isZero {
|
||||
return nil
|
||||
}
|
||||
|
||||
let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Little.rawValue | (opaque ? CGImageAlphaInfo.noneSkipFirst.rawValue : CGImageAlphaInfo.premultipliedFirst.rawValue))
|
||||
|
||||
guard let context = CGContext(data: bytes, width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: bitmapInfo.rawValue) else {
|
||||
return nil
|
||||
let context = DrawingContext(size: size, scale: scale ?? 0.0, opaque: opaque, clear: false)
|
||||
context.withContext { c in
|
||||
rotatedContext(context.size, c)
|
||||
}
|
||||
|
||||
context.scaleBy(x: selectedScale, y: selectedScale)
|
||||
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
|
||||
context.scaleBy(x: 1.0, y: -1.0)
|
||||
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
|
||||
|
||||
rotatedContext(size, context)
|
||||
|
||||
guard let image = CGImage(width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: bitmapInfo, provider: provider, decode: nil, shouldInterpolate: false, intent: .defaultIntent)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return UIImage(cgImage: image, scale: selectedScale, orientation: .up)
|
||||
return context.generateImage()
|
||||
}
|
||||
|
||||
public func generateFilledCircleImage(diameter: CGFloat, color: UIColor?, strokeColor: UIColor? = nil, strokeWidth: CGFloat? = nil, backgroundColor: UIColor? = nil) -> UIImage? {
|
||||
@ -433,6 +372,102 @@ public enum DrawingContextBltMode {
|
||||
case Alpha
|
||||
}
|
||||
|
||||
public func getSharedDevideGraphicsContextSettings() -> DeviceGraphicsContextSettings {
|
||||
struct OpaqueSettings {
|
||||
let rowAlignment: Int
|
||||
let bitsPerPixel: Int
|
||||
let bitsPerComponent: Int
|
||||
let opaqueBitmapInfo: CGBitmapInfo
|
||||
let colorSpace: CGColorSpace
|
||||
|
||||
init(context: CGContext) {
|
||||
self.rowAlignment = context.bytesPerRow
|
||||
self.bitsPerPixel = context.bitsPerPixel
|
||||
self.bitsPerComponent = context.bitsPerComponent
|
||||
self.opaqueBitmapInfo = context.bitmapInfo
|
||||
if #available(iOS 10.0, *) {
|
||||
if UIScreen.main.traitCollection.displayGamut == .P3 {
|
||||
self.colorSpace = CGColorSpace(name: CGColorSpace.displayP3) ?? context.colorSpace!
|
||||
} else {
|
||||
self.colorSpace = context.colorSpace!
|
||||
}
|
||||
} else {
|
||||
self.colorSpace = context.colorSpace!
|
||||
}
|
||||
assert(self.rowAlignment == 32)
|
||||
assert(self.bitsPerPixel == 32)
|
||||
assert(self.bitsPerComponent == 8)
|
||||
}
|
||||
}
|
||||
|
||||
struct TransparentSettings {
|
||||
let transparentBitmapInfo: CGBitmapInfo
|
||||
|
||||
init(context: CGContext) {
|
||||
self.transparentBitmapInfo = context.bitmapInfo
|
||||
}
|
||||
}
|
||||
|
||||
var opaqueSettings: OpaqueSettings?
|
||||
var transparentSettings: TransparentSettings?
|
||||
|
||||
if #available(iOS 10.0, *) {
|
||||
let opaqueFormat = UIGraphicsImageRendererFormat()
|
||||
let transparentFormat = UIGraphicsImageRendererFormat()
|
||||
if #available(iOS 12.0, *) {
|
||||
opaqueFormat.preferredRange = .standard
|
||||
transparentFormat.preferredRange = .standard
|
||||
}
|
||||
opaqueFormat.opaque = true
|
||||
transparentFormat.opaque = false
|
||||
|
||||
let opaqueRenderer = UIGraphicsImageRenderer(bounds: CGRect(origin: CGPoint(), size: CGSize(width: 1.0, height: 1.0)), format: opaqueFormat)
|
||||
let _ = opaqueRenderer.image(actions: { context in
|
||||
opaqueSettings = OpaqueSettings(context: context.cgContext)
|
||||
})
|
||||
|
||||
let transparentRenderer = UIGraphicsImageRenderer(bounds: CGRect(origin: CGPoint(), size: CGSize(width: 1.0, height: 1.0)), format: transparentFormat)
|
||||
let _ = transparentRenderer.image(actions: { context in
|
||||
transparentSettings = TransparentSettings(context: context.cgContext)
|
||||
})
|
||||
} else {
|
||||
UIGraphicsBeginImageContextWithOptions(CGSize(width: 1.0, height: 1.0), true, 1.0)
|
||||
let refContext = UIGraphicsGetCurrentContext()!
|
||||
opaqueSettings = OpaqueSettings(context: refContext)
|
||||
UIGraphicsEndImageContext()
|
||||
|
||||
UIGraphicsBeginImageContextWithOptions(CGSize(width: 1.0, height: 1.0), false, 1.0)
|
||||
let refCtxTransparent = UIGraphicsGetCurrentContext()!
|
||||
transparentSettings = TransparentSettings(context: refCtxTransparent)
|
||||
UIGraphicsEndImageContext()
|
||||
}
|
||||
|
||||
return DeviceGraphicsContextSettings(
|
||||
rowAlignment: opaqueSettings!.rowAlignment,
|
||||
bitsPerPixel: opaqueSettings!.bitsPerPixel,
|
||||
bitsPerComponent: opaqueSettings!.bitsPerComponent,
|
||||
opaqueBitmapInfo: opaqueSettings!.opaqueBitmapInfo,
|
||||
transparentBitmapInfo: transparentSettings!.transparentBitmapInfo,
|
||||
colorSpace: opaqueSettings!.colorSpace
|
||||
)
|
||||
}
|
||||
|
||||
public struct DeviceGraphicsContextSettings {
|
||||
public static let shared: DeviceGraphicsContextSettings = getSharedDevideGraphicsContextSettings()
|
||||
|
||||
public let rowAlignment: Int
|
||||
public let bitsPerPixel: Int
|
||||
public let bitsPerComponent: Int
|
||||
public let opaqueBitmapInfo: CGBitmapInfo
|
||||
public let transparentBitmapInfo: CGBitmapInfo
|
||||
public let colorSpace: CGColorSpace
|
||||
|
||||
public func bytesPerRow(forWidth width: Int) -> Int {
|
||||
let baseValue = self.bitsPerPixel * width / 8
|
||||
return (baseValue + 31) & ~0x1F
|
||||
}
|
||||
}
|
||||
|
||||
public class DrawingContext {
|
||||
public let size: CGSize
|
||||
public let scale: CGFloat
|
||||
@ -440,46 +475,39 @@ public class DrawingContext {
|
||||
public let bytesPerRow: Int
|
||||
private let bitmapInfo: CGBitmapInfo
|
||||
public let length: Int
|
||||
public let bytes: UnsafeMutableRawPointer
|
||||
let provider: CGDataProvider?
|
||||
|
||||
private var _context: CGContext?
|
||||
private let imageBuffer: ASCGImageBuffer
|
||||
public var bytes: UnsafeMutableRawPointer {
|
||||
if self.hasGeneratedImage {
|
||||
preconditionFailure()
|
||||
}
|
||||
return self.imageBuffer.mutableBytes
|
||||
}
|
||||
private let context: CGContext
|
||||
|
||||
private var hasGeneratedImage = false
|
||||
|
||||
public func withContext(_ f: (CGContext) -> ()) {
|
||||
if self._context == nil {
|
||||
if let c = CGContext(data: bytes, width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: self.bitmapInfo.rawValue) {
|
||||
c.scaleBy(x: scale, y: scale)
|
||||
self._context = c
|
||||
}
|
||||
}
|
||||
|
||||
if let _context = self._context {
|
||||
_context.translateBy(x: self.size.width / 2.0, y: self.size.height / 2.0)
|
||||
_context.scaleBy(x: 1.0, y: -1.0)
|
||||
_context.translateBy(x: -self.size.width / 2.0, y: -self.size.height / 2.0)
|
||||
|
||||
f(_context)
|
||||
|
||||
_context.translateBy(x: self.size.width / 2.0, y: self.size.height / 2.0)
|
||||
_context.scaleBy(x: 1.0, y: -1.0)
|
||||
_context.translateBy(x: -self.size.width / 2.0, y: -self.size.height / 2.0)
|
||||
}
|
||||
let context = self.context
|
||||
|
||||
context.translateBy(x: self.size.width / 2.0, y: self.size.height / 2.0)
|
||||
context.scaleBy(x: 1.0, y: -1.0)
|
||||
context.translateBy(x: -self.size.width / 2.0, y: -self.size.height / 2.0)
|
||||
|
||||
f(context)
|
||||
|
||||
context.translateBy(x: self.size.width / 2.0, y: self.size.height / 2.0)
|
||||
context.scaleBy(x: 1.0, y: -1.0)
|
||||
context.translateBy(x: -self.size.width / 2.0, y: -self.size.height / 2.0)
|
||||
}
|
||||
|
||||
public func withFlippedContext(_ f: (CGContext) -> ()) {
|
||||
if self._context == nil {
|
||||
if let c = CGContext(data: bytes, width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: self.bitmapInfo.rawValue) {
|
||||
c.scaleBy(x: scale, y: scale)
|
||||
self._context = c
|
||||
}
|
||||
}
|
||||
|
||||
if let _context = self._context {
|
||||
f(_context)
|
||||
}
|
||||
f(self.context)
|
||||
}
|
||||
|
||||
public init(size: CGSize, scale: CGFloat = 0.0, premultiplied: Bool = true, opaque: Bool = false, clear: Bool = false) {
|
||||
public init(size: CGSize, scale: CGFloat = 0.0, opaque: Bool = false, clear: Bool = false) {
|
||||
assert(!size.width.isZero && !size.height.isZero)
|
||||
let size: CGSize = CGSize(width: max(1.0, size.width), height: max(1.0, size.height))
|
||||
|
||||
let actualScale: CGFloat
|
||||
if scale.isZero {
|
||||
actualScale = deviceScale
|
||||
@ -490,35 +518,61 @@ public class DrawingContext {
|
||||
self.scale = actualScale
|
||||
self.scaledSize = CGSize(width: size.width * actualScale, height: size.height * actualScale)
|
||||
|
||||
self.bytesPerRow = (4 * Int(scaledSize.width) + 15) & (~15)
|
||||
self.bytesPerRow = DeviceGraphicsContextSettings.shared.bytesPerRow(forWidth: Int(scaledSize.width))
|
||||
self.length = bytesPerRow * Int(scaledSize.height)
|
||||
|
||||
self.imageBuffer = ASCGImageBuffer(length: UInt(self.length))
|
||||
|
||||
if opaque {
|
||||
self.bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.noneSkipFirst.rawValue)
|
||||
} else if premultiplied {
|
||||
self.bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue)
|
||||
self.bitmapInfo = DeviceGraphicsContextSettings.shared.opaqueBitmapInfo
|
||||
} else {
|
||||
self.bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.first.rawValue)
|
||||
self.bitmapInfo = DeviceGraphicsContextSettings.shared.transparentBitmapInfo
|
||||
}
|
||||
|
||||
self.bytes = malloc(length)!
|
||||
|
||||
self.context = CGContext(
|
||||
data: self.imageBuffer.mutableBytes,
|
||||
width: Int(self.scaledSize.width),
|
||||
height: Int(self.scaledSize.height),
|
||||
bitsPerComponent: DeviceGraphicsContextSettings.shared.bitsPerComponent,
|
||||
bytesPerRow: self.bytesPerRow,
|
||||
space: DeviceGraphicsContextSettings.shared.colorSpace,
|
||||
bitmapInfo: self.bitmapInfo.rawValue,
|
||||
releaseCallback: nil,
|
||||
releaseInfo: nil
|
||||
)!
|
||||
self.context.scaleBy(x: self.scale, y: self.scale)
|
||||
|
||||
if clear {
|
||||
memset(self.bytes, 0, self.length)
|
||||
}
|
||||
self.provider = CGDataProvider(dataInfo: bytes, data: bytes, size: length, releaseData: { bytes, _, _ in
|
||||
free(bytes)
|
||||
})
|
||||
|
||||
assert(self.bytesPerRow % 16 == 0)
|
||||
assert(Int64(Int(bitPattern: self.bytes)) % 16 == 0)
|
||||
}
|
||||
|
||||
public func generateImage() -> UIImage? {
|
||||
if self.scaledSize.width.isZero || self.scaledSize.height.isZero {
|
||||
return nil
|
||||
}
|
||||
if let image = CGImage(width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: bitmapInfo, provider: provider!, decode: nil, shouldInterpolate: false, intent: .defaultIntent) {
|
||||
return UIImage(cgImage: image, scale: scale, orientation: .up)
|
||||
if self.hasGeneratedImage {
|
||||
preconditionFailure()
|
||||
return nil
|
||||
}
|
||||
self.hasGeneratedImage = true
|
||||
|
||||
let dataProvider = self.imageBuffer.createDataProviderAndInvalidate()
|
||||
|
||||
if let image = CGImage(
|
||||
width: Int(self.scaledSize.width),
|
||||
height: Int(self.scaledSize.height),
|
||||
bitsPerComponent: self.context.bitsPerComponent,
|
||||
bitsPerPixel: self.context.bitsPerPixel,
|
||||
bytesPerRow: self.context.bytesPerRow,
|
||||
space: DeviceGraphicsContextSettings.shared.colorSpace,
|
||||
bitmapInfo: self.context.bitmapInfo,
|
||||
provider: dataProvider,
|
||||
decode: nil,
|
||||
shouldInterpolate: true,
|
||||
intent: .defaultIntent
|
||||
) {
|
||||
return UIImage(cgImage: image, scale: self.scale, orientation: .up)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
@ -126,7 +126,7 @@ public class ImageNode: ASDisplayNode {
|
||||
private let hasImage: ValuePromise<Bool>?
|
||||
private var first = true
|
||||
private let enableEmpty: Bool
|
||||
private let enableAnimatedTransition: Bool
|
||||
public var enableAnimatedTransition: Bool
|
||||
|
||||
private let _contentReady = Promise<Bool>()
|
||||
private var didSetReady: Bool = false
|
||||
|
@ -122,7 +122,7 @@ open class LegacyPresentedController: ViewController {
|
||||
override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition)
|
||||
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
|
||||
}
|
||||
|
||||
override open func dismiss(completion: (() -> Void)? = nil) {
|
||||
|
@ -157,7 +157,7 @@ private func generateRectsImage(color: UIColor, rects: [CGRect], inset: CGFloat,
|
||||
|
||||
public final class LinkHighlightingNode: ASDisplayNode {
|
||||
private var rects: [CGRect] = []
|
||||
private let imageNode: ASImageNode
|
||||
public let imageNode: ASImageNode
|
||||
|
||||
public var innerRadius: CGFloat = 4.0
|
||||
public var outerRadius: CGFloat = 4.0
|
||||
@ -196,7 +196,7 @@ public final class LinkHighlightingNode: ASDisplayNode {
|
||||
}
|
||||
|
||||
private func updateImage() {
|
||||
if rects.isEmpty {
|
||||
if self.rects.isEmpty {
|
||||
self.imageNode.image = nil
|
||||
}
|
||||
let (offset, image) = generateRectsImage(color: self.color, rects: self.rects, inset: self.inset, outerRadius: self.outerRadius, innerRadius: self.innerRadius)
|
||||
@ -206,6 +206,19 @@ public final class LinkHighlightingNode: ASDisplayNode {
|
||||
self.imageNode.frame = CGRect(origin: offset, size: image.size)
|
||||
}
|
||||
}
|
||||
|
||||
public static func generateImage(color: UIColor, inset: CGFloat, innerRadius: CGFloat, outerRadius: CGFloat, rects: [CGRect]) -> (CGPoint, UIImage)? {
|
||||
if rects.isEmpty {
|
||||
return nil
|
||||
}
|
||||
let (offset, image) = generateRectsImage(color: color, rects: rects, inset: inset, outerRadius: outerRadius, innerRadius: innerRadius)
|
||||
|
||||
if let image = image {
|
||||
return (offset, image)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public func asyncLayout() -> (UIColor, [CGRect], CGFloat, CGFloat, CGFloat) -> () -> Void {
|
||||
let currentRects = self.rects
|
||||
|
@ -3307,7 +3307,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture
|
||||
switch curve {
|
||||
case .linear:
|
||||
headerNode.layer.animateBoundsOriginYAdditive(from: offset, to: 0.0, duration: duration, mediaTimingFunction: CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear))
|
||||
case .spring:
|
||||
case .spring, .customSpring:
|
||||
transition.0.animateOffsetAdditive(node: headerNode, offset: offset)
|
||||
case let .custom(p1, p2, p3, p4):
|
||||
headerNode.layer.animateBoundsOriginYAdditive(from: offset, to: 0.0, duration: duration, mediaTimingFunction: CAMediaTimingFunction(controlPoints: p1, p2, p3, p4))
|
||||
|
@ -192,7 +192,7 @@ public func listViewAnimationDurationAndCurve(transition: ContainedViewLayoutTra
|
||||
return (animationDuration, .Default(duration: animationDuration))
|
||||
case .easeInOut:
|
||||
return (animationDuration, .Default(duration: animationDuration))
|
||||
case .spring:
|
||||
case .spring, .customSpring:
|
||||
return (animationDuration, .Spring(duration: animationDuration))
|
||||
case let .custom(c1x, c1y, c2x, c2y):
|
||||
return (animationDuration, .Custom(duration: animationDuration, c1x, c1y, c2x, c2y))
|
||||
|
@ -547,7 +547,10 @@ open class ListViewItemNode: ASDisplayNode, AccessibilityFocusableNode {
|
||||
|
||||
public func updateFrame(_ frame: CGRect, within containerSize: CGSize) {
|
||||
self.frame = frame
|
||||
self.updateAbsoluteRect(frame, within: containerSize)
|
||||
if frame.maxY < 0.0 || frame.minY > containerSize.height {
|
||||
} else {
|
||||
self.updateAbsoluteRect(frame, within: containerSize)
|
||||
}
|
||||
if let extractedBackgroundNode = self.extractedBackgroundNode {
|
||||
extractedBackgroundNode.frame = frame.offsetBy(dx: 0.0, dy: -self.insets.top)
|
||||
}
|
||||
|
@ -32,6 +32,8 @@ public final class ListViewTransactionQueue {
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
assert(true)
|
||||
}
|
||||
}
|
||||
|
||||
|