Merge branch 'master' into experiments/openh264

This commit is contained in:
Ali 2021-06-24 22:34:59 +04:00
commit 22f53979ac
943 changed files with 28711 additions and 15358 deletions

View File

@ -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",
],
)

View File

@ -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)

View File

@ -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>

View File

@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 889 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 917 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@ -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";

View File

@ -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",
)

View File

@ -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()

View File

@ -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,

View File

@ -8,4 +8,5 @@ exports_files([
"WatchApp.mobileprovision",
"WatchExtension.mobileprovision",
"Widget.mobileprovision",
"BroadcastUpload.mobileprovision",
])

View File

@ -1 +1 @@
2100
2300

View File

@ -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"

View File

@ -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

View File

@ -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)
}

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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 {

View File

@ -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)

View File

@ -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() {

View File

@ -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

View File

@ -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() {

View File

@ -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() {

View File

@ -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? {

View File

@ -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() {

View File

@ -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 {

View File

@ -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() {

View File

@ -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

View File

@ -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() {

View File

@ -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")

View File

@ -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]) {

View File

@ -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)
}

View File

@ -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)

View File

@ -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 {

View File

@ -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])

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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() {

View File

@ -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() {

View File

@ -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)

View File

@ -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
}
}

View File

@ -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?

View File

@ -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()
}
}

View File

@ -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() {

View File

@ -19,6 +19,7 @@ swift_library(
"//submodules/PresentationDataUtils:PresentationDataUtils",
"//submodules/OverlayStatusController:OverlayStatusController",
"//submodules/AccountContext:AccountContext",
"//submodules/AppBundle:AppBundle",
],
visibility = [
"//visibility:public",

View File

@ -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
}

View File

@ -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

View File

@ -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()
}

View File

@ -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) {

View File

@ -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)

View File

@ -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

View File

@ -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 {

View File

@ -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
}

View File

@ -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

View File

@ -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) {

View File

@ -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

View File

@ -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))

View File

@ -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))

View File

@ -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)
}

View File

@ -32,6 +32,8 @@ public final class ListViewTransactionQueue {
}
}
})
} else {
assert(true)
}
}

Some files were not shown because too many files have changed in this diff Show More